diff --git a/Directory.Packages.props b/Directory.Packages.props
index 5909b9f25be7..090afcd2160d 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,23 +12,23 @@
-
+
-
-
-
-
+
+
+
+
-
+
-
-
+
+
@@ -46,10 +46,10 @@
-
-
+
+
-
+
@@ -57,9 +57,9 @@
-
-
-
+
+
+
@@ -74,17 +74,17 @@
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/DatatypeItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/DatatypeItemControllerBase.cs
index ab7881f7a438..f082e86b3ff5 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/DatatypeItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/Item/DatatypeItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.DataType}")]
[ApiExplorerSettings(GroupName = "Data Type")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes)]
public class DatatypeItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Item/DictionaryItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Item/DictionaryItemControllerBase.cs
index c64f67572b04..0859d28fa57c 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Item/DictionaryItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Dictionary/Item/DictionaryItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Dictionary.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/dictionary")]
[ApiExplorerSettings(GroupName = "Dictionary")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessDictionary)]
public class DictionaryItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/DocumentItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/DocumentItemControllerBase.cs
index 700a0d4734c9..04a08643e30d 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/DocumentItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Document/Item/DocumentItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Document.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Document}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Document))]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessDocuments)]
public class DocumentItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/DocumentBlueprintItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/DocumentBlueprintItemControllerBase.cs
index 4e204aa91885..61c65268f8a3 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/DocumentBlueprintItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentBlueprint/Item/DocumentBlueprintItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentBlueprint.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.DocumentBlueprint}")]
[ApiExplorerSettings(GroupName = "Document Blueprint")]
-[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)]
public class DocumentBlueprintItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/DocumentTypeItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/DocumentTypeItemControllerBase.cs
index 16e2a3af9baf..b9e6e9873b78 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/DocumentTypeItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/DocumentType/Item/DocumentTypeItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.DocumentType.Item;
[VersionedApiBackOfficeRoute( $"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.DocumentType}")]
[ApiExplorerSettings(GroupName = "Document Type")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentTypes)]
public class DocumentTypeItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Language/Item/LanguageItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Language/Item/LanguageItemControllerBase.cs
index 1e416bdee637..bdb89a129073 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Language/Item/LanguageItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Language/Item/LanguageItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Language.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Language}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Language))]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessLanguages)]
public class LanguageItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/MediaItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/MediaItemControllerBase.cs
index ad773156756f..f2cce0a87df5 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/MediaItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Media/Item/MediaItemControllerBase.cs
@@ -1,15 +1,11 @@
-using Asp.Versioning;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Media.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Media}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Media))]
-[Authorize(Policy = AuthorizationPolicies.SectionAccessForMediaTree)]
public class MediaItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/FolderMediaTypeItemController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/FolderMediaTypeItemController.cs
new file mode 100644
index 000000000000..970ed0af2809
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/FolderMediaTypeItemController.cs
@@ -0,0 +1,37 @@
+using Asp.Versioning;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Umbraco.Cms.Api.Management.ViewModels.MediaType.Item;
+using Umbraco.Cms.Core.Mapping;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services.ContentTypeEditing;
+
+namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Item;
+
+[ApiVersion("1.0")]
+public class FolderMediaTypeItemController : MediaTypeItemControllerBase
+{
+ private readonly IMediaTypeEditingService _mediaTypeEditingService;
+ private readonly IUmbracoMapper _mapper;
+
+ public FolderMediaTypeItemController(IMediaTypeEditingService mediaTypeEditingService, IUmbracoMapper mapper)
+ {
+ _mediaTypeEditingService = mediaTypeEditingService;
+ _mapper = mapper;
+ }
+
+ [HttpGet("folders")]
+ [MapToApiVersion("1.0")]
+ [ProducesResponseType(typeof(PagedModel), StatusCodes.Status200OK)]
+ public async Task Folders(CancellationToken cancellationToken, int skip = 0, int take = 100)
+ {
+ PagedModel mediaTypes = await _mediaTypeEditingService.GetFolderMediaTypes(skip, take);
+
+ var result = new PagedModel
+ {
+ Items = _mapper.MapEnumerable(mediaTypes.Items),
+ Total = mediaTypes.Total
+ };
+ return Ok(result);
+ }
+}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/MediaTypeItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/MediaTypeItemControllerBase.cs
index 3ed737b93268..d19be41c40c6 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/MediaTypeItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Item/MediaTypeItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.MediaType}")]
[ApiExplorerSettings(GroupName = "Media Type")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessMediaTypes)]
public class MediaTypeItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs
index 87d1c7d201d4..641ca0924598 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/MediaType/Tree/MediaTypeTreeControllerBase.cs
@@ -8,6 +8,7 @@
using Umbraco.Cms.Core.Models.Entities;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Web.Common.Authorization;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Api.Management.Controllers.MediaType.Tree;
@@ -38,6 +39,7 @@ protected override MediaTypeTreeItemResponseModel[] MapTreeItemViewModels(Guid?
if (mediaTypes.TryGetValue(entity.Id, out IMediaType? mediaType))
{
responseModel.Icon = mediaType.Icon ?? responseModel.Icon;
+ responseModel.IsDeletable = mediaType.IsSystemMediaType() is false;
}
return responseModel;
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/MemberItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/MemberItemControllerBase.cs
index fba91bd72ea1..8a653f57ad10 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/MemberItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Member/Item/MemberItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Member.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Member}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Member))]
-[Authorize(Policy = AuthorizationPolicies.SectionAccessForMemberTree)]
public class MemberItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/MemberGroupItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/MemberGroupItemControllerBase.cs
index 926677156c1a..c6116e9a66dd 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/MemberGroupItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberGroup/Item/MemberGroupItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.MemberGroup.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.MemberGroup}")]
[ApiExplorerSettings(GroupName = "Member Group")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberGroups)]
public class MemberGroupItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs
index 1276370095b4..286a4ac1cd01 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Item/PartialViewItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.PartialView.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.PartialView}")]
[ApiExplorerSettings(GroupName = "Partial View")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessPartialViews)]
public class PartialViewItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/RelationTypeItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/RelationTypeItemControllerBase.cs
index 9daa3ac10e4e..5ca45c513523 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/RelationTypeItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/RelationType/Item/RelationTypeItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.RelationType.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.RelationType}")]
[ApiExplorerSettings(GroupName = "Relation Type")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessRelationTypes)]
public class RelationTypeItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ScriptItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ScriptItemControllerBase.cs
index 9da3dea41129..51c0c52456fb 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ScriptItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Item/ScriptItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Script.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Script}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Script))]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessScripts)]
public class ScriptItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/StylesheetItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/StylesheetItemControllerBase.cs
index 17a7f01125d7..ea16eca4401a 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/StylesheetItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Item/StylesheetItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Stylesheet}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Stylesheet))]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)]
public class StylesheetItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/TemplateItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/TemplateItemControllerBase.cs
index 19941bd6d7fa..acb531401c2c 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/TemplateItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Template/Item/TemplateItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Template.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Template}")]
[ApiExplorerSettings(GroupName = nameof(Constants.UdiEntityType.Template))]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessTemplates)]
public class TemplateItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/Item/UserGroupItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/Item/UserGroupItemControllerBase.cs
index 70664a5d1996..7c3a60454a9c 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/Item/UserGroupItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/UserGroup/Item/UserGroupItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.UserGroup.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/user-group")]
[ApiExplorerSettings(GroupName = "User Group")]
-[Authorize(Policy = AuthorizationPolicies.SectionAccessUsers)]
public class UserGroupItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/Item/WebhookItemControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/Item/WebhookItemControllerBase.cs
index 30b8ef5ac7f0..1a6d41d43f7e 100644
--- a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/Item/WebhookItemControllerBase.cs
+++ b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/Item/WebhookItemControllerBase.cs
@@ -1,14 +1,11 @@
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Api.Management.Routing;
using Umbraco.Cms.Core;
-using Umbraco.Cms.Web.Common.Authorization;
namespace Umbraco.Cms.Api.Management.Controllers.Webhook.Item;
[VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Item}/{Constants.UdiEntityType.Webhook}")]
[ApiExplorerSettings(GroupName = "Webhook")]
-[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)]
public class WebhookItemControllerBase : ManagementApiControllerBase
{
}
diff --git a/src/Umbraco.Cms.Api.Management/Factories/DataTypeReferencePresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DataTypeReferencePresentationFactory.cs
index aa6b150a94bb..25382c72093a 100644
--- a/src/Umbraco.Cms.Api.Management/Factories/DataTypeReferencePresentationFactory.cs
+++ b/src/Umbraco.Cms.Api.Management/Factories/DataTypeReferencePresentationFactory.cs
@@ -47,8 +47,13 @@ public IEnumerable CreateDataTypeReferenceViewMo
IEnumerable propertyAliases = propertyAliasesByGuid[contentType.Key];
yield return new DataTypeReferenceResponseModel
{
- Id = contentType.Key,
- Type = usagesByEntityType.Key,
+ ContentType = new DataTypeContentTypeReferenceModel
+ {
+ Id = contentType.Key,
+ Name = contentType.Name,
+ Icon = contentType.Icon,
+ Type = usagesByEntityType.Key,
+ },
Properties = contentType
.PropertyTypes
.Where(propertyType => propertyAliases.InvariantContains(propertyType.Alias))
diff --git a/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs b/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs
index 543968b73e68..32fdf427b865 100644
--- a/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs
+++ b/src/Umbraco.Cms.Api.Management/Mapping/MediaType/MediaTypeMapDefinition.cs
@@ -48,6 +48,8 @@ private void Map(IMediaType source, MediaTypeResponseModel target, MapperContext
MediaType = referenceByIdModel,
CompositionType = compositionType,
});
+ target.IsDeletable = source.IsSystemMediaType() is false;
+ target.AliasCanBeChanged = source.IsSystemMediaType() is false;
}
// Umbraco.Code.MapAll
diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json
index 3da37e114958..6cd20dc3ce41 100644
--- a/src/Umbraco.Cms.Api.Management/OpenApi.json
+++ b/src/Umbraco.Cms.Api.Management/OpenApi.json
@@ -13217,6 +13217,61 @@
]
}
},
+ "/umbraco/management/api/v1/item/media-type/folders": {
+ "get": {
+ "tags": [
+ "Media Type"
+ ],
+ "operationId": "GetItemMediaTypeFolders",
+ "parameters": [
+ {
+ "name": "skip",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 0
+ }
+ },
+ {
+ "name": "take",
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "default": 100
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/PagedModelMediaTypeItemResponseModel"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "The resource is protected and requires an authentication token"
+ },
+ "403": {
+ "description": "The authenticated user do not have access to this resource"
+ }
+ },
+ "security": [
+ {
+ "Backoffice User": [ ]
+ }
+ ]
+ }
+ },
"/umbraco/management/api/v1/item/media-type/search": {
"get": {
"tags": [
@@ -35344,6 +35399,34 @@
],
"type": "string"
},
+ "DataTypeContentTypeReferenceModel": {
+ "required": [
+ "icon",
+ "id",
+ "name",
+ "type"
+ ],
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "uuid"
+ },
+ "type": {
+ "type": "string",
+ "nullable": true
+ },
+ "name": {
+ "type": "string",
+ "nullable": true
+ },
+ "icon": {
+ "type": "string",
+ "nullable": true
+ }
+ },
+ "additionalProperties": false
+ },
"DataTypeItemResponseModel": {
"required": [
"id",
@@ -35402,18 +35485,17 @@
},
"DataTypeReferenceResponseModel": {
"required": [
- "id",
- "properties",
- "type"
+ "contentType",
+ "properties"
],
"type": "object",
"properties": {
- "id": {
- "type": "string",
- "format": "uuid"
- },
- "type": {
- "type": "string"
+ "contentType": {
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/DataTypeContentTypeReferenceModel"
+ }
+ ]
},
"properties": {
"type": "array",
@@ -38587,12 +38669,14 @@
"MediaTypeResponseModel": {
"required": [
"alias",
+ "aliasCanBeChanged",
"allowedAsRoot",
"allowedMediaTypes",
"compositions",
"containers",
"icon",
"id",
+ "isDeletable",
"isElement",
"name",
"properties",
@@ -38680,6 +38764,12 @@
}
]
}
+ },
+ "isDeletable": {
+ "type": "boolean"
+ },
+ "aliasCanBeChanged": {
+ "type": "boolean"
}
},
"additionalProperties": false
@@ -38710,6 +38800,7 @@
"hasChildren",
"icon",
"id",
+ "isDeletable",
"isFolder",
"name"
],
@@ -38738,6 +38829,9 @@
},
"icon": {
"type": "string"
+ },
+ "isDeletable": {
+ "type": "boolean"
}
},
"additionalProperties": false
@@ -45027,4 +45121,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeContentTypeReferenceModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeContentTypeReferenceModel.cs
new file mode 100644
index 000000000000..d89033584d5b
--- /dev/null
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeContentTypeReferenceModel.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Cms.Api.Management.ViewModels.DataType;
+
+public class DataTypeContentTypeReferenceModel
+{
+ public required Guid Id { get; set; }
+
+ public required string? Type { get; set; }
+
+ public required string? Name { get; set; }
+
+ public required string? Icon { get; set; }
+}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeReferenceResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeReferenceResponseModel.cs
index 7c9c54a72d4a..2aa7dc39d511 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeReferenceResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/DataType/DataTypeReferenceResponseModel.cs
@@ -2,9 +2,7 @@
public class DataTypeReferenceResponseModel
{
- public required Guid Id { get; init; }
-
- public required string Type { get; init; }
+ public required DataTypeContentTypeReferenceModel ContentType { get; init; }
public required IEnumerable Properties { get; init; }
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs
index 19112c6a877b..552e899e256a 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/MediaType/MediaTypeResponseModel.cs
@@ -7,4 +7,8 @@ public class MediaTypeResponseModel : ContentTypeResponseModelBase AllowedMediaTypes { get; set; } = Enumerable.Empty();
public IEnumerable Compositions { get; set; } = Enumerable.Empty();
+
+ public bool IsDeletable { get; set; }
+
+ public bool AliasCanBeChanged { get; set; }
}
diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs
index ccb0fed5e2a9..f1f8f55c38ea 100644
--- a/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs
+++ b/src/Umbraco.Cms.Api.Management/ViewModels/Tree/MediaTypeTreeItemResponseModel.cs
@@ -3,4 +3,6 @@
public class MediaTypeTreeItemResponseModel : FolderTreeItemResponseModel
{
public string Icon { get; set; } = string.Empty;
+
+ public bool IsDeletable { get; set; }
}
diff --git a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs
index c71243a199a2..d46a22ac44cf 100644
--- a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs
+++ b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs
@@ -23,7 +23,15 @@ public static void RefreshPublicAccess(this DistributedCache dc)
#region UserCacheRefresher
public static void RemoveUserCache(this DistributedCache dc, IEnumerable users)
- => dc.Remove(UserCacheRefresher.UniqueId, users.Select(x => x.Id).Distinct().ToArray());
+ {
+ IEnumerable payloads = users.Select(x => new UserCacheRefresher.JsonPayload()
+ {
+ Id = x.Id,
+ Key = x.Key,
+ });
+
+ dc.RefreshByPayload(UserCacheRefresher.UniqueId, payloads);
+ }
public static void RefreshUserCache(this DistributedCache dc, IEnumerable users)
{
diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index 3d10a6b2675a..e10356fd96ec 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -20,8 +20,8 @@ public class ContentSettings
internal const string StaticDisallowedUploadFiles = "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,xamlx";
internal const bool StaticShowDeprecatedPropertyEditors = false;
internal const string StaticLoginBackgroundImage = "login/login.jpg";
- internal const string StaticLoginLogoImage = "login/logo_dark.svg";
- internal const string StaticLoginLogoImageAlternative = "login/logo_light.svg";
+ internal const string StaticLoginLogoImage = "login/logo_light.svg";
+ internal const string StaticLoginLogoImageAlternative = "login/logo_dark.svg";
internal const bool StaticHideBackOfficeLogo = false;
internal const bool StaticDisableDeleteWhenReferenced = false;
internal const bool StaticDisableUnpublishWhenReferenced = false;
@@ -80,8 +80,8 @@ public class ContentSettings
/// of a light background (e.g. in mobile resolutions).
///
/// This is the alternative version to the regular logo found at .
- [DefaultValue(StaticLoginLogoImage)]
- public string LoginLogoImageAlternative { get; set; } = StaticLoginLogoImage;
+ [DefaultValue(StaticLoginLogoImageAlternative)]
+ public string LoginLogoImageAlternative { get; set; } = StaticLoginLogoImageAlternative;
///
/// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not.
diff --git a/src/Umbraco.Core/Configuration/Models/PackageManifestSettings.cs b/src/Umbraco.Core/Configuration/Models/PackageManifestSettings.cs
deleted file mode 100644
index f353281ddde9..000000000000
--- a/src/Umbraco.Core/Configuration/Models/PackageManifestSettings.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Umbraco.Cms.Core.Configuration.Models;
-
-///
-/// Typed configuration options for package manifest settings.
-///
-[UmbracoOptions(Constants.Configuration.ConfigPackageManifests)]
-public class PackageManifestSettings
-{
- public TimeSpan CacheTimeout { get; set; } = TimeSpan.FromMinutes(10);
-}
diff --git a/src/Umbraco.Core/Constants-Validation.cs b/src/Umbraco.Core/Constants-Validation.cs
index 9024730337b9..98aa2fc67eba 100644
--- a/src/Umbraco.Core/Constants-Validation.cs
+++ b/src/Umbraco.Core/Constants-Validation.cs
@@ -8,11 +8,11 @@ public static class ErrorMessages
{
public static class Properties
{
- public const string Missing = "#validation.invalidNull";
+ public const string Missing = "#validation_invalidNull";
- public const string Empty = "#validation.invalidEmpty";
+ public const string Empty = "#validation_invalidEmpty";
- public const string PatternMismatch = "#validation.invalidPattern";
+ public const string PatternMismatch = "#validation_invalidPattern";
}
}
}
diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
index f11aa5d982dc..6832bbe78971 100644
--- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
+++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs
@@ -85,7 +85,6 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder)
.AddUmbracoOptions()
.AddUmbracoOptions()
.AddUmbracoOptions()
- .AddUmbracoOptions()
.AddUmbracoOptions();
// Configure connection string and ensure it's updated when the configuration changes
diff --git a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
index 4ff3782019a5..38f9bf15ffe3 100644
--- a/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
+++ b/src/Umbraco.Core/Extensions/ClaimsIdentityExtensions.cs
@@ -65,7 +65,7 @@ public static class ClaimsIdentityExtensions
if (identity is ClaimsIdentity claimsIdentity)
{
userId = claimsIdentity.FindFirstValue(ClaimTypes.NameIdentifier)
- ?? claimsIdentity.FindFirstValue("sub");
+ ?? claimsIdentity.FindFirstValue(Constants.Security.OpenIdDictSubClaimType);
}
return userId;
@@ -88,7 +88,7 @@ public static class ClaimsIdentityExtensions
string? userKey = null;
if (identity is ClaimsIdentity claimsIdentity)
{
- userKey = claimsIdentity.FindFirstValue("sub");
+ userKey = claimsIdentity.FindFirstValue(Constants.Security.OpenIdDictSubClaimType);
}
return Guid.TryParse(userKey, out Guid result)
diff --git a/src/Umbraco.Core/Models/MediaType.cs b/src/Umbraco.Core/Models/MediaType.cs
index 64683ae462a9..7e5e572cd406 100644
--- a/src/Umbraco.Core/Models/MediaType.cs
+++ b/src/Umbraco.Core/Models/MediaType.cs
@@ -1,5 +1,6 @@
using System.Runtime.Serialization;
using Umbraco.Cms.Core.Strings;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Models;
@@ -45,6 +46,21 @@ public MediaType(IShortStringHelper shortStringHelper, IMediaType parent, string
///
public override ISimpleContentType ToSimple() => new SimpleContentType(this);
+ ///
+ public override string Alias
+ {
+ get => base.Alias;
+ set
+ {
+ if (this.IsSystemMediaType() && value != Alias)
+ {
+ throw new InvalidOperationException("Cannot change the alias of a system media type");
+ }
+
+ base.Alias = value;
+ }
+ }
+
///
IMediaType IMediaType.DeepCloneWithResetIdentities(string newAlias) =>
(IMediaType)DeepCloneWithResetIdentities(newAlias);
diff --git a/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs b/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs
index d628537ab32b..ade1da8b8af2 100644
--- a/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs
+++ b/src/Umbraco.Core/PropertyEditors/BlockGridConfiguration.cs
@@ -26,6 +26,10 @@ public class BlockGridBlockConfiguration : IBlockConfiguration
public Guid ContentElementTypeKey { get; set; }
public Guid? SettingsElementTypeKey { get; set; }
+
+ public bool AllowAtRoot { get; set; }
+
+ public bool AllowInAreas { get; set; }
}
public class NumberRange
diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/IMediaTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/IMediaTypeEditingService.cs
index 36b9ef40e2f3..e712fc66a2c1 100644
--- a/src/Umbraco.Core/Services/ContentTypeEditing/IMediaTypeEditingService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeEditing/IMediaTypeEditingService.cs
@@ -16,4 +16,6 @@ public interface IMediaTypeEditingService
IEnumerable currentPropertyAliases);
Task> GetMediaTypesForFileExtensionAsync(string fileExtension, int skip, int take);
+
+ Task> GetFolderMediaTypes(int skip, int take);
}
diff --git a/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs b/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs
index 47fc1b8f7a12..11def4a3338b 100644
--- a/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeEditing/MediaTypeEditingService.cs
@@ -42,6 +42,11 @@ internal sealed class MediaTypeEditingService : ContentTypeEditingServiceBase> UpdateAsync(IMediaType mediaType, MediaTypeUpdateModel model, Guid userKey)
{
+ if (mediaType.IsSystemMediaType() && mediaType.Alias != model.Alias)
+ {
+ return Attempt.FailWithStatus(ContentTypeOperationStatus.NotAllowed, null);
+ }
+
Attempt result = await ValidateAndMapForUpdateAsync(mediaType, model);
if (result.Success)
{
@@ -102,6 +107,35 @@ public async Task> GetMediaTypesForFileExtensionAsync(str
}
+ public Task> GetFolderMediaTypes(int skip, int take)
+ {
+ // we'll consider it a "folder" media type if it:
+ // - does not contain an umbracoFile property
+ // - has any allowed types below itself
+ var folderMediaTypes = _mediaTypeService
+ .GetAll()
+ .Where(mt =>
+ mt.CompositionPropertyTypes.Any(pt => pt.Alias == Constants.Conventions.Media.File) is false
+ && mt.AllowedContentTypes?.Any() is true)
+ .ToList();
+
+ // as a special case, the "Folder" system media type must always be included
+ if (folderMediaTypes.Any(mediaType => mediaType.Alias == Constants.Conventions.MediaTypes.Folder) is false)
+ {
+ IMediaType? defaultFolderMediaType = _mediaTypeService.Get(Constants.Conventions.MediaTypes.Folder);
+ if (defaultFolderMediaType is not null)
+ {
+ folderMediaTypes.Add(defaultFolderMediaType);
+ }
+ }
+
+ return Task.FromResult(new PagedModel
+ {
+ Items = folderMediaTypes.Skip(skip).Take(take),
+ Total = folderMediaTypes.Count
+ });
+ }
+
protected override IMediaType CreateContentType(IShortStringHelper shortStringHelper, int parentId)
=> new MediaType(shortStringHelper, parentId);
diff --git a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index f44ebb3a28f0..8340f3c58478 100644
--- a/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -620,6 +620,11 @@ public async Task DeleteAsync(Guid key, Guid perform
return ContentTypeOperationStatus.NotFound;
}
+ if (CanDelete(item) is false)
+ {
+ return ContentTypeOperationStatus.NotAllowed;
+ }
+
Delete(item, performingUserId);
scope.Complete();
@@ -628,6 +633,11 @@ public async Task DeleteAsync(Guid key, Guid perform
public void Delete(TItem item, int userId = Constants.Security.SuperUserId)
{
+ if (CanDelete(item) is false)
+ {
+ throw new InvalidOperationException("The item was not allowed to be deleted");
+ }
+
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
EventMessages eventMessages = EventMessagesFactory.Get();
@@ -695,6 +705,10 @@ public void Delete(TItem item, int userId = Constants.Security.SuperUserId)
public void Delete(IEnumerable items, int userId = Constants.Security.SuperUserId)
{
TItem[] itemsA = items.ToArray();
+ if (itemsA.All(CanDelete) is false)
+ {
+ throw new InvalidOperationException("One or more items were not allowed to be deleted");
+ }
using (ICoreScope scope = ScopeProvider.CreateCoreScope())
{
@@ -750,6 +764,8 @@ public void Delete(IEnumerable items, int userId = Constants.Security.Sup
protected abstract void DeleteItemsOfTypes(IEnumerable typeIds);
+ protected virtual bool CanDelete(TItem item) => true;
+
#endregion
#region Copy
diff --git a/src/Umbraco.Core/Services/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/EntityXmlSerializer.cs
index b22fcfd46ec6..872b6b686857 100644
--- a/src/Umbraco.Core/Services/EntityXmlSerializer.cs
+++ b/src/Umbraco.Core/Services/EntityXmlSerializer.cs
@@ -211,6 +211,7 @@ public XElement Serialize(IDataType dataType)
// The 'ID' when exporting is actually the property editor alias (in pre v7 it was the IDataType GUID id)
xml.Add(new XAttribute("Id", dataType.EditorAlias));
+ xml.Add(new XAttribute("EditorUiAlias", dataType.EditorUiAlias ?? dataType.EditorAlias));
xml.Add(new XAttribute("Definition", dataType.Key));
xml.Add(new XAttribute("DatabaseType", dataType.DatabaseType.ToString()));
xml.Add(new XAttribute("Configuration", _configurationEditorJsonSerializer.Serialize(dataType.ConfigurationObject)));
diff --git a/src/Umbraco.Core/Services/MediaTypeService.cs b/src/Umbraco.Core/Services/MediaTypeService.cs
index cc914e43bc66..359b4a99a946 100644
--- a/src/Umbraco.Core/Services/MediaTypeService.cs
+++ b/src/Umbraco.Core/Services/MediaTypeService.cs
@@ -8,6 +8,7 @@
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Services.Locking;
+using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Services;
@@ -77,6 +78,9 @@ protected override void DeleteItemsOfTypes(IEnumerable typeIds)
}
}
+ protected override bool CanDelete(IMediaType item)
+ => item.IsSystemMediaType() is false;
+
#region Notifications
protected override SavingNotification GetSavingNotification(
diff --git a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs
index e161a7171a3e..fbf00b7fa323 100644
--- a/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs
+++ b/src/Umbraco.Infrastructure/Manifest/PackageManifestService.cs
@@ -10,16 +10,18 @@ internal sealed class PackageManifestService : IPackageManifestService
{
private readonly IEnumerable _packageManifestReaders;
private readonly IAppPolicyCache _cache;
- private readonly PackageManifestSettings _packageManifestSettings;
+ private RuntimeSettings _runtimeSettings;
+
public PackageManifestService(
IEnumerable packageManifestReaders,
AppCaches appCaches,
- IOptions packageManifestSettings)
+ IOptionsMonitor runtimeSettingsOptionsMonitor)
{
_packageManifestReaders = packageManifestReaders;
- _packageManifestSettings = packageManifestSettings.Value;
_cache = appCaches.RuntimeCache;
+ _runtimeSettings = runtimeSettingsOptionsMonitor.CurrentValue;
+ runtimeSettingsOptionsMonitor.OnChange(runtimeSettings => _runtimeSettings = runtimeSettings);
}
public async Task> GetAllPackageManifestsAsync()
@@ -34,7 +36,9 @@ public async Task> GetAllPackageManifestsAsync()
return tasks.SelectMany(x => x.Result);
},
- _packageManifestSettings.CacheTimeout)
+ _runtimeSettings.Mode == RuntimeMode.Production
+ ? TimeSpan.FromDays(30)
+ : TimeSpan.FromSeconds(10))
?? Array.Empty();
public async Task> GetPublicPackageManifestsAsync()
diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
index 3e9bd6ffdc28..009811bc7d48 100644
--- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs
@@ -1062,6 +1062,7 @@ private void CreateContentTypeData()
Thumbnail = Constants.Icons.MediaFolder,
AllowAtRoot = true,
Variations = (byte)ContentVariation.Nothing,
+ ListView = Constants.DataTypes.Guids.ListViewMediaGuid
});
}
@@ -1886,11 +1887,13 @@ void InsertDataTypeDto(int id, string editorAlias, string editorUiAlias, string
}
// layouts for the list view
- const string cardLayout =
- "{\"name\": \"Grid\",\"path\": \"views/propertyeditors/listview/layouts/grid/grid.html\", \"icon\": \"icon-thumbnails-small\", \"isSystem\": true, \"selected\": true}";
- const string listLayout =
- "{\"name\": \"List\",\"path\": \"views/propertyeditors/listview/layouts/list/list.html\",\"icon\": \"icon-list\", \"isSystem\": true,\"selected\": true}";
- const string layouts = "[" + cardLayout + "," + listLayout + "]";
+ string TableCollectionView(string collectionViewType) =>
+ $"{{\"name\": \"List\",\"collectionView\": \"Umb.CollectionView.{collectionViewType}.Table\", \"icon\": \"icon-list\", \"isSystem\": true, \"selected\": true}}";
+
+ string GridCollectionView(string collectionViewType) =>
+ $"{{\"name\": \"Grid\",\"collectionView\": \"Umb.CollectionView.{collectionViewType}.Grid\",\"icon\": \"icon-thumbnails-small\", \"isSystem\": true,\"selected\": true}}";
+
+ string Layouts(string collectionViewType) => $"[{GridCollectionView(collectionViewType)},{TableCollectionView(collectionViewType)}]";
// Insert data types only if the corresponding Node record exists (which may or may not have been created depending on configuration
// of data types to create).
@@ -2094,7 +2097,7 @@ void InsertDataTypeDto(int id, string editorAlias, string editorUiAlias, string
DbType = "Nvarchar",
Configuration =
"{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" +
- layouts +
+ Layouts("Document") +
", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":true},{\"alias\":\"creator\",\"header\":\"Updated by\",\"isSystem\":true}]}",
});
}
@@ -2113,7 +2116,7 @@ void InsertDataTypeDto(int id, string editorAlias, string editorUiAlias, string
DbType = "Nvarchar",
Configuration =
"{\"pageSize\":100, \"orderBy\":\"updateDate\", \"orderDirection\":\"desc\", \"layouts\":" +
- layouts +
+ Layouts("Media") +
", \"includeProperties\":[{\"alias\":\"updateDate\",\"header\":\"Last edited\",\"isSystem\":true},{\"alias\":\"creator\",\"header\":\"Updated by\",\"isSystem\":true}]}",
});
}
diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs b/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs
index ab6d079a96de..cbdccc1ca420 100644
--- a/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs
+++ b/src/Umbraco.Infrastructure/Migrations/MigrationBase.cs
@@ -91,6 +91,11 @@ protected MigrationBase(IMigrationContext context)
///
public bool RebuildCache { get; set; }
+ ///
+ /// If this is set to true, all backoffice client tokens will be revoked upon successful completion of the migration.
+ ///
+ public bool InvalidateBackofficeUserAccess { get; set; }
+
///
/// Runs the migration.
///
diff --git a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
index 31a9a3fa5f5c..bf2d73430541 100644
--- a/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
+++ b/src/Umbraco.Infrastructure/Migrations/MigrationPlanExecutor.cs
@@ -1,11 +1,15 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
+using OpenIddict.Abstractions;
+using Org.BouncyCastle.Utilities;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Migrations;
+using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Scoping;
+using Umbraco.Cms.Core.Security;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Infrastructure.Persistence;
using Umbraco.Cms.Infrastructure.Scoping;
@@ -42,10 +46,12 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IPublishedSnapshotService _publishedSnapshotService;
private readonly IKeyValueService _keyValueService;
+ private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly DistributedCache _distributedCache;
private readonly IScopeAccessor _scopeAccessor;
private readonly ICoreScopeProvider _scopeProvider;
private bool _rebuildCache;
+ private bool _invalidateBackofficeUserAccess;
public MigrationPlanExecutor(
ICoreScopeProvider scopeProvider,
@@ -55,7 +61,8 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
IUmbracoDatabaseFactory databaseFactory,
IPublishedSnapshotService publishedSnapshotService,
DistributedCache distributedCache,
- IKeyValueService keyValueService)
+ IKeyValueService keyValueService,
+ IServiceScopeFactory serviceScopeFactory)
{
_scopeProvider = scopeProvider;
_scopeAccessor = scopeAccessor;
@@ -64,6 +71,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
_databaseFactory = databaseFactory;
_publishedSnapshotService = publishedSnapshotService;
_keyValueService = keyValueService;
+ _serviceScopeFactory = serviceScopeFactory;
_distributedCache = distributedCache;
_logger = _loggerFactory.CreateLogger();
}
@@ -85,7 +93,8 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService(),
- StaticServiceProvider.Instance.GetRequiredService())
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService())
{
}
@@ -103,8 +112,8 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService(),
StaticServiceProvider.Instance.GetRequiredService(),
- StaticServiceProvider.Instance.GetRequiredService()
- )
+ StaticServiceProvider.Instance.GetRequiredService(),
+ StaticServiceProvider.Instance.GetRequiredService())
{
}
@@ -135,6 +144,12 @@ public ExecutedMigrationPlan ExecutePlan(MigrationPlan plan, string fromState)
RebuildCache();
}
+ // If any completed migration requires us to sign out the user we'll do that.
+ if (_invalidateBackofficeUserAccess)
+ {
+ RevokeBackofficeTokens().GetAwaiter().GetResult(); // should async all the way up at some point
+ }
+
return result;
}
@@ -320,6 +335,11 @@ private void RunMigration(Type migrationType, MigrationContext context)
{
_rebuildCache = true;
}
+
+ if (migration.InvalidateBackofficeUserAccess)
+ {
+ _invalidateBackofficeUserAccess = true;
+ }
}
private void RebuildCache()
@@ -327,4 +347,31 @@ private void RebuildCache()
_publishedSnapshotService.RebuildAll();
_distributedCache.RefreshAllPublishedSnapshot();
}
+
+ private async Task RevokeBackofficeTokens()
+ {
+ using IServiceScope scope = _serviceScopeFactory.CreateScope();
+
+ IOpenIddictApplicationManager openIddictApplicationManager = scope.ServiceProvider.GetRequiredService();
+ var backOfficeClient = await openIddictApplicationManager.FindByClientIdAsync(Constants.OAuthClientIds.BackOffice);
+ if (backOfficeClient is null)
+ {
+ _logger.LogWarning("Could not get the openIddict Application for {backofficeClientId}. Canceling token revocation. Users might have to manually log out to get proper access to the backoffice", Constants.OAuthClientIds.BackOffice);
+ return;
+ }
+
+ var backOfficeClientId = await openIddictApplicationManager.GetIdAsync(backOfficeClient);
+ if (backOfficeClientId is null)
+ {
+ _logger.LogWarning("Could not extract the clientId from the openIddict backofficelient Application. Canceling token revocation. Users might have to manually log out to get proper access to the backoffice", Constants.OAuthClientIds.BackOffice);
+ return;
+ }
+
+ IOpenIddictTokenManager tokenManager = scope.ServiceProvider.GetRequiredService();
+ var tokens = await tokenManager.FindByApplicationIdAsync(backOfficeClientId).ToArrayAsync();
+ foreach (var token in tokens)
+ {
+ await tokenManager.DeleteAsync(token);
+ }
+ }
}
diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs
index e5edce8e870c..fe730fd2b83c 100644
--- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs
+++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUsers.cs
@@ -26,6 +26,7 @@ public AddGuidsToUsers(IMigrationContext context, IScopeProvider scopeProvider)
protected override void Migrate()
{
+ InvalidateBackofficeUserAccess = true;
using IScope scope = _scopeProvider.CreateScope();
using IDisposable notificationSuppression = scope.Notifications.Suppress();
ScopeDatabase(scope);
diff --git a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
index 32be1b1cc1ed..a6c3ea453e38 100644
--- a/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
+++ b/src/Umbraco.Infrastructure/Packaging/PackageDataInstallation.cs
@@ -1257,12 +1257,15 @@ public IReadOnlyList ImportDataTypes(IReadOnlyCollection da
editor = new VoidEditor(_dataValueEditorFactory) {Alias = editorAlias ?? string.Empty};
}
+ var editorUiAlias = dataTypeElement.Attribute("EditorUiAlias")?.Value?.Trim() ?? editorAlias;
+
var dataType = new DataType(editor, _serializer)
{
Key = dataTypeDefinitionId,
Name = dataTypeDefinitionName,
DatabaseType = databaseType,
- ParentId = parentId
+ ParentId = parentId,
+ EditorUiAlias = editorUiAlias,
};
var configurationAttributeValue = dataTypeElement.Attribute("Configuration")?.Value;
diff --git a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
index 23ae35cb406b..c24a982e008e 100644
--- a/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
+++ b/src/Umbraco.Infrastructure/Security/BackOfficeUserStore.cs
@@ -157,8 +157,9 @@ public async Task ValidateSessionIdAsync(string? userId, string? sessionId
throw new DataException("Could not create the user, check logs for details");
}
- // re-assign id
+ // re-assign id and key
user.Id = UserIdToString(userEntity.Id);
+ user.Key = userEntity.Key;
if (isLoginsPropertyDirty)
{
diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client
index fb470a9625b7..6abe0e675e4d 160000
--- a/src/Umbraco.Web.UI.Client
+++ b/src/Umbraco.Web.UI.Client
@@ -1 +1 @@
-Subproject commit fb470a9625b7a04ef1a33322fa36c3b5480a11e9
+Subproject commit 6abe0e675e4d41570e9df980f44041051f3c8950
diff --git a/src/Umbraco.Web.UI.Login/index.html b/src/Umbraco.Web.UI.Login/index.html
index ba0c0f314187..db8045b5dee3 100644
--- a/src/Umbraco.Web.UI.Login/index.html
+++ b/src/Umbraco.Web.UI.Login/index.html
@@ -48,7 +48,7 @@
{
const input = document.createElement('input');
input.type = opts.type;
@@ -28,6 +29,7 @@ const createInput = (opts: {
input.required = true;
input.inputMode = opts.inputmode;
input.ariaLabel = opts.label;
+ input.autofocus = opts.autofocus || false;
return input;
};
@@ -171,6 +173,7 @@ export default class UmbAuthElement extends UmbLitElement {
autocomplete: 'username',
label: labelUsername,
inputmode: this.usernameIsEmail ? 'email' : '',
+ autofocus: true,
});
this._passwordInput = createInput({
id: 'password-input',
diff --git a/src/Umbraco.Web.UI.New/umbraco/Data/Umbraco.sqlite.db b/src/Umbraco.Web.UI.New/umbraco/Data/Umbraco.sqlite.db
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props
index 78cb5a571ddb..4fce9e86f31e 100644
--- a/tests/Directory.Packages.props
+++ b/tests/Directory.Packages.props
@@ -5,9 +5,9 @@
-
+
-
+
@@ -17,7 +17,7 @@
-
+
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json
index 98ae006aec9b..00eec9388acb 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.46",
+ "@umbraco/playwright-testhelpers": "^2.0.0-beta.49",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"faker": "^4.1.0",
@@ -146,9 +146,9 @@
"integrity": "sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg=="
},
"node_modules/@umbraco/playwright-testhelpers": {
- "version": "2.0.0-beta.46",
- "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.46.tgz",
- "integrity": "sha512-SJKmKO/84QFnCy0j7fGEYbRtbLZKC/k1xlyUKrkZpzVekVIS6gSki5ECWu4LiJnfmo+yhxGBsA2l3iLZfL1gow==",
+ "version": "2.0.0-beta.49",
+ "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.49.tgz",
+ "integrity": "sha512-fmEJjuawY8QEHLQ9xozp83GozkVFFwEAHMYnNENYt98XSyu55OA7sRxIXUabKPOUGzgcGkiSceicKG7JXBoofw==",
"dependencies": {
"@umbraco/json-models-builders": "2.0.6",
"camelize": "^1.0.0",
diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json
index 3cc0dfdc902e..baabb52f3f09 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/package.json
+++ b/tests/Umbraco.Tests.AcceptanceTest/package.json
@@ -22,7 +22,7 @@
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.5",
- "@umbraco/playwright-testhelpers": "^2.0.0-beta.46",
+ "@umbraco/playwright-testhelpers": "^2.0.0-beta.49",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"faker": "^4.1.0",
diff --git a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts
index e512b25d68fd..c0b1d0710713 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts
@@ -19,7 +19,7 @@ export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
- retries: process.env.CI ? 3 : 2,
+ retries: process.env.CI ? 2 : 1,
// We don't want to run parallel, as tests might differ in state
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts
new file mode 100644
index 000000000000..e12749b1cd43
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts
@@ -0,0 +1,150 @@
+import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers';
+import {expect} from '@playwright/test';
+
+const documentTypeName = 'TestDocumentType';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoUi.goToBackOffice();
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+});
+
+test('can create a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.clickActionsMenuAtRoot();
+ await umbracoUi.documentType.clickCreateButton();
+ await umbracoUi.documentType.clickCreateDocumentTypeButton();
+ await umbracoUi.documentType.enterDocumentTypeName(documentTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ await umbracoUi.documentType.reloadTree('Document Types');
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName);
+});
+
+test('can create a document type with a template', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoApi.template.ensureNameNotExists(documentTypeName);
+
+ // Act
+ await umbracoUi.documentType.clickActionsMenuAtRoot();
+ await umbracoUi.documentType.clickCreateButton();
+ await umbracoUi.documentType.clickCreateDocumentTypeWithTemplateButton();
+ await umbracoUi.documentType.enterDocumentTypeName(documentTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ // Checks if both the success notification for document Types and teh template are visible
+ await umbracoUi.documentType.doesSuccessNotificationsHaveCount(2);
+ // Checks if the documentType contains the template
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ const templateData = await umbracoApi.template.getByName(documentTypeName);
+ expect(documentTypeData.allowedTemplates[0].id).toEqual(templateData.id);
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+
+ // Clean
+ await umbracoApi.template.ensureNameNotExists(documentTypeName);
+});
+
+test('can create a element type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.clickActionsMenuAtRoot();
+ await umbracoUi.documentType.clickCreateButton();
+ await umbracoUi.documentType.clickCreateElementTypeButton();
+ await umbracoUi.documentType.enterDocumentTypeName(documentTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ // Checks if the isElement is true
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.isElement).toBeTruthy();
+});
+
+test('can rename a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const wrongName = 'NotADocumentTypeName';
+ await umbracoApi.documentType.ensureNameNotExists(wrongName);
+ await umbracoApi.documentType.createDefaultDocumentType(wrongName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(wrongName);
+ await umbracoUi.documentType.enterDocumentTypeName(documentTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ await umbracoUi.documentType.isDocumentTreeItemVisible(wrongName, false);
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName);
+});
+
+test('can update the alias for a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const oldAlias = AliasHelper.toAlias(documentTypeName);
+ const newAlias = 'newDocumentTypeAlias';
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ const documentTypeDataOld = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeDataOld.alias).toBe(oldAlias);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.enterAliasName(newAlias);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName, true);
+ const documentTypeDataNew = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeDataNew.alias).toBe(newAlias);
+});
+
+test('can add an icon for a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const bugIcon = 'icon-bug';
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.updateIcon(bugIcon);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.icon).toBe(bugIcon);
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName, true);
+});
+
+test('can delete a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+
+ // Act
+ await umbracoUi.documentType.clickRootFolderCaretButton();
+ await umbracoUi.documentType.clickActionsMenuForDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDeleteExactButton();
+ await umbracoUi.documentType.clickConfirmToDeleteButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeFalsy();
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts
new file mode 100644
index 000000000000..9254db4605b7
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts
@@ -0,0 +1,431 @@
+import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {expect} from "@playwright/test";
+
+const documentTypeName = 'TestDocumentType';
+const dataTypeName = 'Approved Color';
+const groupName = 'TestGroup';
+const tabName = 'TestTab';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoUi.goToBackOffice();
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+});
+
+test('can add a property to a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickAddGroupButton();
+ await umbracoUi.documentType.addPropertyEditor(dataTypeName);
+ await umbracoUi.documentType.enterGroupName(groupName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ const dataType = await umbracoApi.dataType.getByName(dataTypeName);
+ // Checks if the correct property was added to the document type
+ expect(documentTypeData.properties[0].dataType.id).toBe(dataType.id);
+});
+
+test('can update a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const newDataTypeName = 'Image Media Picker';
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.updatePropertyEditor(newDataTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ const dataType = await umbracoApi.dataType.getByName(newDataTypeName);
+ // Checks if the correct property was added to the document type
+ expect(documentTypeData.properties[0].dataType.id).toBe(dataType.id);
+});
+
+test('can update group name in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const newGroupName = 'UpdatedGroupName';
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.enterGroupName(newGroupName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.containers[0].name).toBe(newGroupName);
+});
+
+test('can delete a group in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.deleteGroup(groupName, true);
+ await umbracoUi.documentType.clickConfirmToDeleteButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.containers.length).toBe(0);
+ expect(documentTypeData.properties.length).toBe(0);
+});
+
+// TODO: Currently I am getting an error If I delete a tab that contains children. The children are not cleaned up when deleting the tab.
+test.skip('can delete a tab in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditorInTab(documentTypeName, dataTypeName, dataTypeData.id, tabName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickRemoveTabWithName(tabName);
+ await umbracoUi.documentType.clickConfirmToDeleteButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+});
+
+test('can delete a property editor in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.deletePropertyEditorWithName(dataTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties.length).toBe(0);
+});
+
+test('can create a document type with a property in a tab', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickAddTabButton();
+ await umbracoUi.documentType.enterTabName(tabName);
+ await umbracoUi.documentType.clickAddGroupButton();
+ await umbracoUi.documentType.addPropertyEditor(dataTypeName, 1);
+ await umbracoUi.documentType.enterGroupName(groupName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(await umbracoApi.documentType.doesTabContainCorrectPropertyEditorInGroup(documentTypeName, dataTypeName, documentTypeData.properties[0].dataType.id, tabName, groupName)).toBeTruthy();
+});
+
+test('can create a document type with multiple groups', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondDataTypeName = 'Image Media Picker';
+ const secondGroupName = 'TesterGroup';
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName);
+ const secondDataType = await umbracoApi.dataType.getByName(secondDataTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickAddGroupButton();
+ await umbracoUi.documentType.enterGroupName(secondGroupName, 1);
+ await umbracoUi.documentType.addPropertyEditor(secondDataTypeName, 1);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ expect(await umbracoApi.documentType.doesGroupContainCorrectPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName)).toBeTruthy();
+ expect(await umbracoApi.documentType.doesGroupContainCorrectPropertyEditor(documentTypeName, secondDataTypeName, secondDataType.id, secondGroupName)).toBeTruthy();
+});
+
+test('can create a document type with multiple tabs', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondDataTypeName = 'Image Media Picker';
+ const secondGroupName = 'TesterGroup';
+ const secondTabName = 'SecondTab';
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditorInTab(documentTypeName, dataTypeName, dataTypeData.id, tabName, groupName);
+ const secondDataType = await umbracoApi.dataType.getByName(secondDataTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickAddTabButton();
+ await umbracoUi.documentType.enterTabName(secondTabName);
+ await umbracoUi.documentType.clickAddGroupButton();
+ await umbracoUi.documentType.enterGroupName(secondGroupName);
+ await umbracoUi.documentType.addPropertyEditor(secondDataTypeName, 1);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy();
+ expect(await umbracoApi.documentType.doesTabContainCorrectPropertyEditorInGroup(documentTypeName, dataTypeName, dataTypeData.id, tabName, groupName)).toBeTruthy();
+ expect(await umbracoApi.documentType.doesTabContainCorrectPropertyEditorInGroup(documentTypeName, secondDataTypeName, secondDataType.id, secondTabName, secondGroupName)).toBeTruthy();
+});
+
+test('can create a document type with a composition', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const compositionDocumentTypeName = 'CompositionDocumentType';
+ await umbracoApi.documentType.ensureNameNotExists(compositionDocumentTypeName);
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const compositionDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(compositionDocumentTypeName, dataTypeName, dataTypeData.id, groupName);
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickCompositionsButton();
+ await umbracoUi.documentType.clickButtonWithName(compositionDocumentTypeName);
+ await umbracoUi.documentType.clickSubmitButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(umbracoUi.documentType.doesGroupHaveValue(groupName)).toBeTruthy();
+ // Checks if the composition in the document type is correct
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.compositions[0].documentType.id).toBe(compositionDocumentTypeId);
+
+ // Clean
+ await umbracoApi.documentType.ensureNameNotExists(compositionDocumentTypeName);
+});
+
+test('can remove a composition form a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const compositionDocumentTypeName = 'CompositionDocumentType';
+ await umbracoApi.documentType.ensureNameNotExists(compositionDocumentTypeName);
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const compositionDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(compositionDocumentTypeName, dataTypeName, dataTypeData.id, groupName);
+ await umbracoApi.documentType.createDocumentTypeWithAComposition(documentTypeName, compositionDocumentTypeId);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickCompositionsButton();
+ await umbracoUi.documentType.clickButtonWithName(compositionDocumentTypeName);
+ await umbracoUi.documentType.clickSubmitButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoUi.documentType.doesGroupHaveValue(groupName)).toBeFalsy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.compositions).toEqual([]);
+
+ // Clean
+ await umbracoApi.documentType.ensureNameNotExists(compositionDocumentTypeName);
+});
+
+test('can reorder groups in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondGroupName = 'SecondGroup';
+ await umbracoApi.documentType.createDocumentTypeWithTwoGroups(documentTypeName, dataTypeName, dataTypeData.id, groupName, secondGroupName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+
+ // Act
+ await umbracoUi.documentType.clickReorderButton();
+ const groupValues = await umbracoUi.documentType.reorderTwoGroups();
+ const firstGroupValue = groupValues.firstGroupValue;
+ const secondGroupValue = groupValues.secondGroupValue;
+ await umbracoUi.documentType.clickIAmDoneReorderingButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ // Since we swapped sorting order, the firstGroupValue should have sortOrder 1 and the secondGroupValue should have sortOrder 0
+ expect(await umbracoApi.documentType.doesDocumentTypeGroupNameContainCorrectSortOrder(documentTypeName, secondGroupValue, 0)).toBeTruthy();
+ expect(await umbracoApi.documentType.doesDocumentTypeGroupNameContainCorrectSortOrder(documentTypeName, firstGroupValue, 1)).toBeTruthy();
+});
+
+// TODO: Unskip when it works. Sometimes the properties are not dragged correctly.
+test.skip('can reorder properties in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const dataTypeNameTwo = "Second Color Picker";
+ await umbracoApi.documentType.createDocumentTypeWithTwoPropertyEditors(documentTypeName, dataTypeName, dataTypeData.id, dataTypeNameTwo, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickReorderButton();
+ // Drag and Drop
+ await umbracoUi.waitForTimeout(5000);
+ const dragFromLocator = umbracoUi.documentType.getTextLocatorWithName(dataTypeNameTwo);
+ const dragToLocator = umbracoUi.documentType.getTextLocatorWithName(dataTypeName);
+ await umbracoUi.documentType.dragAndDrop(dragFromLocator, dragToLocator, 0, 0, 5);
+ await umbracoUi.documentType.clickIAmDoneReorderingButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties[0].name).toBe(dataTypeNameTwo);
+ expect(documentTypeData.properties[1].name).toBe(dataTypeName);
+});
+
+// TODO: Unskip when the frontend does not give the secondTab -1 as the sortOrder
+test.skip('can reorder tabs in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondTabName = 'SecondTab';
+ await umbracoApi.documentType.createDocumentTypeWithTwoTabs(documentTypeName, dataTypeName, dataTypeData.id, tabName, secondTabName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+
+ // Act
+ const dragToLocator = umbracoUi.documentType.getTabLocatorWithName(tabName);
+ const dragFromLocator = umbracoUi.documentType.getTabLocatorWithName(secondTabName);
+ await umbracoUi.documentType.clickReorderButton();
+ await umbracoUi.documentType.dragAndDrop(dragFromLocator, dragToLocator, 0, 0, 10);
+ await umbracoUi.documentType.clickIAmDoneReorderingButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ expect(await umbracoApi.documentType.doesDocumentTypeTabNameContainCorrectSortOrder(documentTypeName, secondTabName, 0)).toBeTruthy();
+ expect(await umbracoApi.documentType.doesDocumentTypeTabNameContainCorrectSortOrder(documentTypeName, tabName, 1)).toBeTruthy();
+});
+
+test('can add a description to a property in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const descriptionText = 'This is a property';
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickEditorSettingsButton();
+ await umbracoUi.documentType.enterPropertyEditorDescription(descriptionText);
+ await umbracoUi.documentType.clickUpdateButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ await expect(umbracoUi.documentType.enterDescriptionTxt).toBeVisible();
+ expect(umbracoUi.documentType.doesDescriptionHaveValue(descriptionText)).toBeTruthy();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties[0].description).toBe(descriptionText);
+});
+
+test('can set is mandatory for a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickEditorSettingsButton();
+ await umbracoUi.documentType.clickMandatorySlider();
+ await umbracoUi.documentType.clickUpdateButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties[0].validation.mandatory).toBeTruthy();
+});
+
+test('can enable validation for a property in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const regex = '^[a-zA-Z0-9]*$';
+ const regexMessage = 'Only letters and numbers are allowed';
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickEditorSettingsButton();
+ await umbracoUi.documentType.selectValidationOption('');
+ await umbracoUi.documentType.enterRegEx(regex);
+ await umbracoUi.documentType.enterRegExMessage(regexMessage);
+ await umbracoUi.documentType.clickUpdateButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties[0].validation.regEx).toBe(regex);
+ expect(documentTypeData.properties[0].validation.regExMessage).toBe(regexMessage);
+});
+
+test('can allow vary by culture for a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, true);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickEditorSettingsButton();
+ await umbracoUi.documentType.clickVaryByCultureSlider();
+ await umbracoUi.documentType.clickUpdateButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties[0].variesByCulture).toBeTruthy();
+});
+
+test('can set appearance to label on top for a property in a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickEditorSettingsButton();
+ await umbracoUi.documentType.clickLabelOnTopButton();
+ await umbracoUi.documentType.clickUpdateButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.properties[0].appearance.labelOnTop).toBeTruthy();
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts
new file mode 100644
index 000000000000..b6a8660ad9d2
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts
@@ -0,0 +1,128 @@
+import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
+import {expect} from '@playwright/test';
+
+const documentFolderName = 'TestFolder';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentFolderName);
+ await umbracoUi.goToBackOffice();
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentFolderName);
+});
+
+test('can create a empty document type folder', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.clickActionsMenuForName('Document Types');
+ await umbracoUi.documentType.clickCreateButton();
+ await umbracoUi.documentType.clickCreateDocumentFolderButton();
+ await umbracoUi.documentType.enterFolderName(documentFolderName);
+ await umbracoUi.documentType.clickCreateFolderButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const folder = await umbracoApi.documentType.getByName(documentFolderName);
+ expect(folder.name).toBe(documentFolderName);
+ // Checks if the folder is in the root
+ await umbracoUi.documentType.reloadTree('Document Types');
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName);
+});
+
+test('can delete a document type folder', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createFolder(documentFolderName);
+
+ // Act
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.clickRootFolderCaretButton();
+ await umbracoUi.documentType.clickActionsMenuForName(documentFolderName);
+ await umbracoUi.documentType.deleteFolder();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoApi.documentType.doesNameExist(documentFolderName);
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName, false);
+});
+
+test('can rename a document type folder', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const oldFolderName = 'OldName';
+ await umbracoApi.documentType.createFolder(oldFolderName);
+
+ // Act
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.clickRootFolderCaretButton();
+ await umbracoUi.documentType.clickActionsMenuForName(oldFolderName);
+ await umbracoUi.documentType.clickRenameFolderButton();
+ await umbracoUi.documentType.enterFolderName(documentFolderName);
+ await umbracoUi.documentType.clickUpdateFolderButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const folder = await umbracoApi.documentType.getByName(documentFolderName);
+ expect(folder.name).toBe(documentFolderName);
+ await umbracoUi.documentType.isDocumentTreeItemVisible(oldFolderName, false);
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName);
+});
+
+test('can create a document type folder in a folder', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const childFolderName = 'ChildFolder';
+ await umbracoApi.documentType.ensureNameNotExists(childFolderName);
+ const parentFolderId = await umbracoApi.documentType.createFolder(documentFolderName);
+
+ // Act
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.clickRootFolderCaretButton();
+ await umbracoUi.documentType.clickActionsMenuForName(documentFolderName);
+ await umbracoUi.documentType.clickCreateButton();
+ await umbracoUi.documentType.clickCreateDocumentFolderButton();
+ await umbracoUi.documentType.enterFolderName(childFolderName);
+ await umbracoUi.documentType.clickCreateFolderButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const folder = await umbracoApi.documentType.getByName(childFolderName);
+ expect(folder.name).toBe(childFolderName);
+ // Checks if the parentFolder contains the ChildFolder as a child
+ const parentFolder = await umbracoApi.documentType.getChildren(parentFolderId);
+ expect(parentFolder[0].name).toBe(childFolderName);
+
+ // Clean
+ await umbracoApi.documentType.ensureNameNotExists(childFolderName);
+});
+
+test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const grandParentFolderName = 'TheGrandFolder';
+ const parentFolderName = 'TheParentFolder';
+ await umbracoApi.documentType.ensureNameNotExists(grandParentFolderName);
+ await umbracoApi.documentType.ensureNameNotExists(parentFolderName);
+ const grandParentFolderId = await umbracoApi.documentType.createFolder(grandParentFolderName);
+ const parentFolderId = await umbracoApi.documentType.createFolder(parentFolderName, grandParentFolderId);
+
+ // Act
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+ await umbracoUi.documentType.clickRootFolderCaretButton();
+ await umbracoUi.documentType.clickCaretButtonForName(grandParentFolderName);
+ await umbracoUi.documentType.clickActionsMenuForName(parentFolderName);
+ await umbracoUi.documentType.clickCreateButton();
+ await umbracoUi.documentType.clickCreateDocumentFolderButton();
+ await umbracoUi.documentType.enterFolderName(documentFolderName);
+ await umbracoUi.documentType.clickCreateFolderButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ await umbracoUi.documentType.reloadTree(parentFolderName);
+ await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName);
+ const grandParentChildren = await umbracoApi.documentType.getChildren(grandParentFolderId);
+ expect(grandParentChildren[0].name).toBe(parentFolderName);
+ const parentChildren = await umbracoApi.documentType.getChildren(parentFolderId);
+ expect(parentChildren[0].name).toBe(documentFolderName);
+
+ // Clean
+ await umbracoApi.documentType.ensureNameNotExists(grandParentFolderName);
+ await umbracoApi.documentType.ensureNameNotExists(parentFolderName);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeSettingsTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeSettingsTab.spec.ts
new file mode 100644
index 000000000000..7463ff804b5a
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeSettingsTab.spec.ts
@@ -0,0 +1,84 @@
+import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {expect} from "@playwright/test";
+
+const documentTypeName = 'TestDocumentType';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoUi.goToBackOffice();
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+});
+
+test('can add allow vary by culture for a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDocumentTypeSettingsTab();
+ await umbracoUi.documentType.clickVaryByCultureButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.variesByCulture).toBeTruthy();
+});
+
+test('can add allow segmentation for a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDocumentTypeSettingsTab();
+ await umbracoUi.documentType.clickVaryBySegmentsButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.variesBySegment).toBeTruthy();
+});
+
+test('can set is an element type for a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDocumentTypeSettingsTab();
+ await umbracoUi.documentType.clickTextButtonWithName('Element type');
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.isElement).toBeTruthy();
+});
+
+// TODO: Unskip. Currently The cleanup is not updated upon save
+test.skip('can disable history cleanup for a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ // Is needed
+ await umbracoUi.waitForTimeout(200);
+ await umbracoUi.documentType.clickDocumentTypeSettingsTab();
+ await umbracoUi.documentType.clickAutoCleanupButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.cleanup.preventCleanup).toBeTruthy();
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts
new file mode 100644
index 000000000000..2bf10c9157b7
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeStructureTab.spec.ts
@@ -0,0 +1,97 @@
+import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {expect} from "@playwright/test";
+
+const documentTypeName = 'TestDocumentType';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoUi.goToBackOffice();
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+});
+
+test('can add allow as root to a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickStructureTab();
+ await umbracoUi.documentType.clickAllowAtRootButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.allowedAsRoot).toBeTruthy();
+});
+
+test('can add an allowed child node to a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickStructureTab();
+ await umbracoUi.documentType.clickChooseButton();
+ await umbracoUi.documentType.clickButtonWithName(documentTypeName);
+ await umbracoUi.documentType.clickAllowedChildNodesButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.allowedDocumentTypes[0].documentType.id).toBe(documentTypeData.id);
+});
+
+test('can remove an allowed child node from a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const childDocumentTypeName = 'ChildDocumentType';
+ await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName);
+ const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName);
+ await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(documentTypeName, childDocumentTypeId);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickStructureTab();
+ await umbracoUi.documentType.clickRemoveButtonForName(childDocumentTypeName);
+ await umbracoUi.documentType.clickConfirmRemoveButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.allowedDocumentTypes.length).toBe(0);
+
+ // Clean
+ await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName);
+});
+
+test('can configure a collection for a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const collectionDataTypeName = 'TestCollection';
+ await umbracoApi.dataType.ensureNameNotExists(collectionDataTypeName);
+ const collectionDataTypeId = await umbracoApi.dataType.create(collectionDataTypeName, 'Umbraco.ListView', [], null, 'Umb.PropertyEditorUi.CollectionView');
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickStructureTab();
+ await umbracoUi.documentType.clickConfigureAsACollectionButton();
+ await umbracoUi.documentType.clickTextButtonWithName(collectionDataTypeName);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.collection.id).toEqual(collectionDataTypeId);
+
+ // Clean
+ await umbracoApi.dataType.ensureNameNotExists(collectionDataTypeName);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeTemplatesTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeTemplatesTab.spec.ts
new file mode 100644
index 000000000000..1390bbdc4f65
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeTemplatesTab.spec.ts
@@ -0,0 +1,78 @@
+import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {expect} from "@playwright/test";
+
+const documentTypeName = 'TestDocumentType';
+const templateName = 'TestTemplate';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+ await umbracoUi.goToBackOffice();
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
+});
+
+test('can add an allowed template to a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.documentType.createDefaultDocumentType(documentTypeName);
+ await umbracoApi.template.ensureNameNotExists(templateName);
+ const templateId = await umbracoApi.template.createDefaultTemplate(templateName);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDocumentTypeTemplatesTab();
+ await umbracoUi.documentType.clickAddButton();
+ await umbracoUi.documentType.clickLabelWithName(templateName);
+ await umbracoUi.documentType.clickChooseButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.allowedTemplates[0].id).toBe(templateId);
+
+ // Clean
+ await umbracoApi.template.ensureNameNotExists(templateName);
+});
+
+test('can set an allowed template as default for document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.template.ensureNameNotExists(templateName);
+ const templateId = await umbracoApi.template.createDefaultTemplate(templateName);
+ await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDocumentTypeTemplatesTab();
+ await umbracoUi.documentType.clickDefaultTemplateButton();
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.allowedTemplates[0].id).toBe(templateId);
+ expect(documentTypeData.defaultTemplate.id).toBe(templateId);
+});
+
+// When removing a template, the defaultTemplateId is set to "" which is not correct
+test.skip('can remove an allowed template from a document type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.template.ensureNameNotExists(templateName);
+ const templateId = await umbracoApi.template.createDefaultTemplate(templateName);
+ await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId);
+ await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
+
+ // Act
+ await umbracoUi.documentType.goToDocumentType(documentTypeName);
+ await umbracoUi.documentType.clickDocumentTypeTemplatesTab();
+ await umbracoUi.documentType.clickRemoveWithName(templateName, true);
+ await umbracoUi.documentType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.documentType.isSuccessNotificationVisible();
+ const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName);
+ expect(documentTypeData.allowedTemplates).toHaveLength(0);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts
index 53d7efd18bd1..4586122d50e3 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts
@@ -1,4 +1,4 @@
-import {test} from '@umbraco/playwright-testhelpers';
+import {test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
const languageName = 'Arabic';
@@ -15,7 +15,7 @@ test.afterEach(async ({umbracoApi}) => {
await umbracoApi.language.ensureNameNotExists(languageName);
});
-test('can add language', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+test.skip('can add language', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
await umbracoUi.language.goToSettingsTreeItem('Language');
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts
new file mode 100644
index 000000000000..96d562206eba
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts
@@ -0,0 +1,94 @@
+import {expect} from "@playwright/test";
+import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers';
+
+const mediaTypeName = 'TestMediaType';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.mediaType.goToSection(ConstantHelper.sections.settings);
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName);
+});
+
+test('can create a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.mediaType.clickActionsMenuForName('Media Types');
+ await umbracoUi.mediaType.clickCreateButton();
+ await umbracoUi.mediaType.clickNewMediaTypeButton();
+ await umbracoUi.mediaType.enterMediaTypeName(mediaTypeName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy();
+});
+
+test('can rename a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const wrongName = 'NotAMediaTypeName';
+ await umbracoApi.mediaType.ensureNameNotExists(wrongName);
+ await umbracoApi.mediaType.createDefaultMediaType(wrongName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(wrongName);
+ await umbracoUi.mediaType.enterMediaTypeName(mediaTypeName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy();
+});
+
+test('can update the alias for a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const oldAlias = AliasHelper.toAlias(mediaTypeName);
+ const updatedAlias = 'TestMediaAlias';
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+ const mediaTypeDataOld = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeDataOld.alias).toBe(oldAlias);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.enterAliasName(updatedAlias);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.alias).toBe(updatedAlias);
+});
+
+test('can add an icon for a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const bugIcon = 'icon-bug';
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.updateIcon(bugIcon);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.icon).toBe(bugIcon);
+ await umbracoUi.mediaType.isTreeItemVisible(mediaTypeName, true);
+});
+
+test('can delete a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.clickRootFolderCaretButton();
+ await umbracoUi.mediaType.clickActionsMenuForName(mediaTypeName);
+ await umbracoUi.mediaType.clickDeleteButton();
+ await umbracoUi.mediaType.clickConfirmToDeleteButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeFalsy();
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts
new file mode 100644
index 000000000000..d344210334aa
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeDesignTab.spec.ts
@@ -0,0 +1,363 @@
+import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {expect} from "@playwright/test";
+
+const mediaTypeName = 'TestMediaType';
+const dataTypeName = 'Upload File';
+const groupName = 'TestGroup';
+const tabName = 'TestTab';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.mediaType.goToSection(ConstantHelper.sections.settings);
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName);
+});
+
+test('can create a media type with a property', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickAddGroupButton();
+ await umbracoUi.mediaType.addPropertyEditor(dataTypeName);
+ await umbracoUi.mediaType.enterGroupName(groupName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ const dataType = await umbracoApi.dataType.getByName(dataTypeName);
+ // Checks if the correct property was added to the media type
+ expect(mediaTypeData.properties[0].dataType.id).toBe(dataType.id);
+});
+
+test('can update a property in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const newDataTypeName = 'Image Media Picker';
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.updatePropertyEditor(newDataTypeName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ const dataType = await umbracoApi.dataType.getByName(newDataTypeName);
+ // Checks if the correct property was added to the media type
+ expect(mediaTypeData.properties[0].dataType.id).toBe(dataType.id);
+});
+
+test('can update group name in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const updatedGroupName = 'UpdatedGroupName';
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id, groupName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.enterGroupName(updatedGroupName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.containers[0].name).toBe(updatedGroupName);
+});
+
+test('can delete a property in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id, groupName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.deletePropertyEditorWithName(dataTypeName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.properties.length).toBe(0);
+});
+
+test('can add a description to property in a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const descriptionText = 'Test Description';
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id, groupName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickEditorSettingsButton();
+ await umbracoUi.mediaType.enterPropertyEditorDescription(descriptionText);
+ await umbracoUi.mediaType.clickUpdateButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ await expect(umbracoUi.mediaType.enterDescriptionTxt).toBeVisible();
+ expect(umbracoUi.mediaType.doesDescriptionHaveValue(descriptionText)).toBeTruthy();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.properties[0].description).toBe(descriptionText);
+});
+
+test('can set a property as mandatory in a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickEditorSettingsButton();
+ await umbracoUi.mediaType.clickMandatorySlider();
+ await umbracoUi.mediaType.clickUpdateButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.properties[0].validation.mandatory).toBeTruthy();
+});
+
+test('can set up validation for a property in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const regex = '^[a-zA-Z0-9]*$';
+ const regexMessage = 'Only letters and numbers are allowed';
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickEditorSettingsButton();
+ await umbracoUi.mediaType.selectValidationOption('');
+ await umbracoUi.mediaType.enterRegEx(regex);
+ await umbracoUi.mediaType.enterRegExMessage(regexMessage);
+ await umbracoUi.mediaType.clickUpdateButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.properties[0].validation.regEx).toBe(regex);
+ expect(mediaTypeData.properties[0].validation.regExMessage).toBe(regexMessage);
+});
+
+test('can set appearance as label on top for property in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickEditorSettingsButton();
+ await umbracoUi.mediaType.clickLabelOnTopButton();
+ await umbracoUi.mediaType.clickUpdateButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.properties[0].appearance.labelOnTop).toBeTruthy();
+});
+
+test('can delete a group in a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id, groupName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.deleteGroup(groupName, true);
+ await umbracoUi.mediaType.clickConfirmToDeleteButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.containers.length).toBe(0);
+ expect(mediaTypeData.properties.length).toBe(0);
+});
+
+test('can create a media type with a property in a tab', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickAddTabButton();
+ await umbracoUi.mediaType.enterTabName(tabName);
+ await umbracoUi.mediaType.addPropertyEditor(dataTypeName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ // Checks if the media type has the correct tab and property
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(await umbracoApi.mediaType.doesTabContainerCorrectPropertyEditor(mediaTypeName, tabName, mediaTypeData.properties[0].dataType.id)).toBeTruthy();
+});
+
+test('can create a media type with multiple groups', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondDataTypeName = 'Image Media Picker';
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id, groupName);
+ const secondDataType = await umbracoApi.dataType.getByName(secondDataTypeName);
+ const secondGroupName = 'TesterGroup';
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickAddGroupButton();
+ await umbracoUi.mediaType.addPropertyEditor(secondDataTypeName, 1);
+ await umbracoUi.mediaType.enterGroupName(secondGroupName, 1);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy();
+ expect(await umbracoApi.mediaType.doesGroupContainCorrectPropertyEditor(mediaTypeName, dataTypeName, dataTypeData.id, groupName)).toBeTruthy();
+ expect(await umbracoApi.mediaType.doesGroupContainCorrectPropertyEditor(mediaTypeName, secondDataTypeName, secondDataType.id, secondGroupName)).toBeTruthy();
+});
+
+test('can create a media type with multiple tabs', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondDataTypeName = 'Image Media Picker';
+ const secondGroupName = 'TesterGroup';
+ const secondTabName = 'SecondTab';
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditorInTab(mediaTypeName, dataTypeName, dataTypeData.id, tabName, groupName);
+ const secondDataType = await umbracoApi.dataType.getByName(secondDataTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickAddTabButton();
+ await umbracoUi.mediaType.enterTabName(secondTabName);
+ await umbracoUi.mediaType.clickAddGroupButton();
+ await umbracoUi.mediaType.enterGroupName(secondGroupName);
+ await umbracoUi.mediaType.addPropertyEditor(secondDataTypeName, 1);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy();
+ expect(await umbracoApi.mediaType.doesTabContainCorrectPropertyEditorInGroup(mediaTypeName, dataTypeName, dataTypeData.id, tabName, groupName)).toBeTruthy();
+ expect(await umbracoApi.mediaType.doesTabContainCorrectPropertyEditorInGroup(mediaTypeName, secondDataTypeName, secondDataType.id, secondTabName, secondGroupName)).toBeTruthy();
+});
+
+test('can delete a tab from a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ await umbracoApi.mediaType.createMediaTypeWithPropertyEditorInTab(mediaTypeName, dataTypeName, dataTypeData.id, tabName, groupName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickRemoveTabWithName(tabName);
+ await umbracoUi.mediaType.clickConfirmToDeleteButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy();
+});
+
+// TODO: Currently there is no composition button, which makes it impossible to test
+test.skip('can create a media type with a composition', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const compositionMediaTypeName = 'CompositionMediaType';
+ await umbracoApi.mediaType.ensureNameNotExists(compositionMediaTypeName);
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const compositionMediaTypeId = await umbracoApi.mediaType.createMediaTypeWithPropertyEditor(compositionMediaTypeName, dataTypeName, dataTypeData.id, groupName);
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickCompositionsButton();
+ await umbracoUi.mediaType.clickButtonWithName(compositionMediaTypeName);
+ await umbracoUi.mediaType.clickSubmitButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(umbracoUi.mediaType.doesGroupHaveValue(groupName)).toBeTruthy();
+ // Checks if the composition in the media type is correct
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.compositions[0].mediaType.id).toBe(compositionMediaTypeId);
+
+ // Clean
+ await umbracoApi.mediaType.ensureNameNotExists(compositionMediaTypeName);
+});
+
+test('can reorder groups in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondGroupName = 'SecondGroup';
+ await umbracoApi.mediaType.createMediaTypeWithTwoGroups(mediaTypeName, dataTypeName, dataTypeData.id, groupName, secondGroupName);
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.clickReorderButton();
+ const groupValues = await umbracoUi.mediaType.reorderTwoGroups();
+ const firstGroupValue = groupValues.firstGroupValue;
+ const secondGroupValue = groupValues.secondGroupValue;
+ await umbracoUi.mediaType.clickIAmDoneReorderingButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ // Since we swapped sorting order, the firstGroupValue should have sortOrder 1 and the secondGroupValue should have sortOrder 0
+ expect(await umbracoApi.mediaType.doesMediaTypeGroupNameContainCorrectSortOrder(mediaTypeName, secondGroupValue, 0)).toBeTruthy();
+ expect(await umbracoApi.mediaType.doesMediaTypeGroupNameContainCorrectSortOrder(mediaTypeName, firstGroupValue, 1)).toBeTruthy();
+});
+
+// TODO: Unskip when it works. Sometimes the properties are not dragged correctly.
+test.skip('can reorder properties in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const dataTypeNameTwo = "Upload Second File";
+ await umbracoApi.mediaType.createMediaTypeWithTwoPropertyEditors(mediaTypeName, dataTypeName, dataTypeData.id, dataTypeNameTwo, dataTypeData.id);
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.clickReorderButton();
+ // Drag and Drop
+ const dragFromLocator = umbracoUi.mediaType.getTextLocatorWithName(dataTypeNameTwo);
+ const dragToLocator = umbracoUi.mediaType.getTextLocatorWithName(dataTypeName);
+ await umbracoUi.mediaType.dragAndDrop(dragFromLocator, dragToLocator, -10, 0, 5);
+ await umbracoUi.waitForTimeout(200);
+ await umbracoUi.mediaType.clickIAmDoneReorderingButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.properties[0].name).toBe(dataTypeNameTwo);
+});
+
+// TODO: Unskip when the frontend does not give the secondTab -1 as the sortOrder
+test.skip('can reorder tabs in a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
+ const secondTabName = 'SecondTab';
+ await umbracoApi.mediaType.createMediaTypeWithTwoTabs(mediaTypeName, dataTypeName, dataTypeData.id, tabName, secondTabName);
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+
+ // Act
+ const dragToLocator = umbracoUi.mediaType.getTabLocatorWithName(tabName);
+ const dragFromLocator = umbracoUi.mediaType.getTabLocatorWithName(secondTabName);
+ await umbracoUi.mediaType.clickReorderButton();
+ await umbracoUi.mediaType.dragAndDrop(dragFromLocator, dragToLocator, 0, 0, 10);
+ await umbracoUi.mediaType.clickIAmDoneReorderingButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesMediaTypeTabNameContainCorrectSortOrder(mediaTypeName, secondTabName, 0)).toBeTruthy();
+ expect(await umbracoApi.mediaType.doesMediaTypeTabNameContainCorrectSortOrder(mediaTypeName, tabName, 1)).toBeTruthy();
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts
new file mode 100644
index 000000000000..7605e1eb7822
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts
@@ -0,0 +1,109 @@
+import {ConstantHelper, test} from '@umbraco/playwright-testhelpers';
+import {expect} from "@playwright/test";
+
+const mediaTypeFolderName = 'TestMediaTypeFolder';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeFolderName);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.mediaType.goToSection(ConstantHelper.sections.settings);
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeFolderName);
+});
+
+test('can create a empty media type folder', async ({umbracoApi, umbracoUi}) => {
+ // Act
+ await umbracoUi.mediaType.clickActionsMenuForName('Media Types');
+ await umbracoUi.mediaType.createFolder(mediaTypeFolderName);
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const folder = await umbracoApi.mediaType.getByName(mediaTypeFolderName);
+ expect(folder.name).toBe(mediaTypeFolderName);
+ // Checks if the folder is in the root
+ await umbracoUi.mediaType.clickRootFolderCaretButton();
+ await umbracoUi.mediaType.isTreeItemVisible(mediaTypeFolderName, true);
+});
+
+test('can delete a media type folder', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.mediaType.createFolder(mediaTypeFolderName);
+
+ // Act
+ await umbracoUi.mediaType.clickRootFolderCaretButton();
+ await umbracoUi.mediaType.clickActionsMenuForName(mediaTypeFolderName);
+ await umbracoUi.mediaType.deleteFolder();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesNameExist(mediaTypeFolderName)).toBeFalsy();
+});
+
+test('can rename a media type folder', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const oldFolderName = 'OldName';
+ await umbracoApi.mediaType.createFolder(oldFolderName);
+
+ // Act
+ await umbracoUi.mediaType.clickRootFolderCaretButton();
+ await umbracoUi.mediaType.clickActionsMenuForName(oldFolderName);
+ await umbracoUi.mediaType.clickRenameFolderButton();
+ await umbracoUi.mediaType.enterFolderName(mediaTypeFolderName);
+ await umbracoUi.mediaType.clickUpdateFolderButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const folder = await umbracoApi.mediaType.getByName(mediaTypeFolderName);
+ expect(folder.name).toBe(mediaTypeFolderName);
+});
+
+test('can create a media type folder in a folder', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const childFolderName = 'ChildFolder';
+ await umbracoApi.mediaType.ensureNameNotExists(childFolderName);
+ const parentFolderId = await umbracoApi.mediaType.createFolder(mediaTypeFolderName);
+
+ // Act
+ await umbracoUi.mediaType.clickRootFolderCaretButton();
+ await umbracoUi.mediaType.clickActionsMenuForName(mediaTypeFolderName);
+ await umbracoUi.mediaType.createFolder(childFolderName);
+
+ // Assert
+ await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName);
+ await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true);
+ const parentFolderChildren = await umbracoApi.mediaType.getChildren(parentFolderId);
+ expect(parentFolderChildren[0].name).toBe(childFolderName);
+
+ // Clean
+ await umbracoApi.mediaType.ensureNameNotExists(childFolderName);
+});
+
+test('can create a media type folder in a folder in a folder', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const grandparentFolderName = 'GrandparentFolder';
+ const childFolderName = 'ChildFolder';
+ await umbracoApi.mediaType.ensureNameNotExists(childFolderName);
+ await umbracoApi.mediaType.ensureNameNotExists(grandparentFolderName);
+ const grandParentFolderId = await umbracoApi.mediaType.createFolder(grandparentFolderName);
+ const parentFolderId = await umbracoApi.mediaType.createFolder(mediaTypeFolderName, grandParentFolderId);
+
+ // Act
+ await umbracoUi.mediaType.clickRootFolderCaretButton();
+ await umbracoUi.mediaType.clickCaretButtonForName(grandparentFolderName);
+ await umbracoUi.mediaType.clickActionsMenuForName(mediaTypeFolderName);
+ await umbracoUi.mediaType.createFolder(childFolderName);
+
+ // Assert
+ await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName);
+ await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true);
+ const grandParentFolderChildren = await umbracoApi.mediaType.getChildren(grandParentFolderId);
+ expect(grandParentFolderChildren[0].name).toBe(mediaTypeFolderName);
+ const parentFolderChildren = await umbracoApi.mediaType.getChildren(parentFolderId);
+ expect(parentFolderChildren[0].name).toBe(childFolderName);
+
+ // Clean
+ await umbracoApi.mediaType.ensureNameNotExists(childFolderName);
+ await umbracoApi.mediaType.ensureNameNotExists(grandparentFolderName);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts
new file mode 100644
index 000000000000..fda909044323
--- /dev/null
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeStructureTab.spec.ts
@@ -0,0 +1,118 @@
+import {ConstantHelper, test} from "@umbraco/playwright-testhelpers";
+import {expect} from "@playwright/test";
+
+const mediaTypeName = 'TestMediaType';
+
+test.beforeEach(async ({umbracoUi, umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName);
+ await umbracoUi.goToBackOffice();
+ await umbracoUi.mediaType.goToSection(ConstantHelper.sections.settings);
+});
+
+test.afterEach(async ({umbracoApi}) => {
+ await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName);
+});
+
+test('can create a media type with allow at root enabled', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickStructureTab();
+ await umbracoUi.mediaType.clickAllowAtRootButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.allowedAsRoot).toBeTruthy();
+});
+
+test('can create a media type with an allowed child node type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickStructureTab();
+ await umbracoUi.mediaType.clickChooseButton();
+ await umbracoUi.mediaType.clickButtonWithName(mediaTypeName);
+ await umbracoUi.mediaType.clickAllowedChildNodesButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.allowedMediaTypes[0].mediaType.id).toBe(mediaTypeData.id);
+});
+
+test('can create a media type with multiple allowed child nodes types', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const mediaTypeId = await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+ const secondMediaTypeName = 'SecondMediaType';
+ await umbracoApi.mediaType.ensureNameNotExists(secondMediaTypeName);
+ const secondMediaTypeId = await umbracoApi.mediaType.createDefaultMediaType(secondMediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickStructureTab();
+ await umbracoUi.mediaType.clickChooseButton();
+ await umbracoUi.mediaType.clickButtonWithName(mediaTypeName);
+ await umbracoUi.mediaType.clickButtonWithName(secondMediaTypeName);
+ await umbracoUi.mediaType.clickAllowedChildNodesButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ expect(await umbracoApi.mediaType.doesMediaTypeContainAllowedChildNodeIds(mediaTypeName, [mediaTypeId, secondMediaTypeId])).toBeTruthy();
+
+ // Clean
+ await umbracoApi.mediaType.ensureNameNotExists(secondMediaTypeName);
+});
+
+test('can delete an allowed child note from a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const childNodeName = 'MediaChildNode';
+ await umbracoApi.mediaType.ensureNameNotExists(childNodeName);
+ const childNodeId = await umbracoApi.mediaType.createDefaultMediaType(childNodeName);
+ await umbracoApi.mediaType.createMediaTypeWithAllowedChildNode(mediaTypeName, childNodeId);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickStructureTab();
+ await umbracoUi.mediaType.clickRemoveButtonForName(childNodeName);
+ await umbracoUi.mediaType.clickConfirmRemoveButton();
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(childNodeName);
+ expect(mediaTypeData.allowedMediaTypes.length).toBe(0);
+
+ // Clean
+ await umbracoApi.mediaType.ensureNameNotExists(childNodeName);
+});
+
+test('can configure a collection for a media type', async ({umbracoApi, umbracoUi}) => {
+ // Arrange
+ const collectionDataTypeName = 'TestCollection';
+ await umbracoApi.dataType.ensureNameNotExists(collectionDataTypeName);
+ const collectionDataTypeId = await umbracoApi.dataType.create(collectionDataTypeName, 'Umbraco.ListView', [], null, 'Umb.PropertyEditorUi.CollectionView');
+ await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName);
+
+ // Act
+ await umbracoUi.mediaType.goToMediaType(mediaTypeName);
+ await umbracoUi.mediaType.clickStructureTab();
+ await umbracoUi.mediaType.clickConfigureAsACollectionButton();
+ await umbracoUi.mediaType.clickTextButtonWithName(collectionDataTypeName);
+ await umbracoUi.mediaType.clickSaveButton();
+
+ // Assert
+ await umbracoUi.mediaType.isSuccessNotificationVisible();
+ const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName);
+ expect(mediaTypeData.collection.id).toEqual(collectionDataTypeId);
+
+ // Clean
+ await umbracoApi.dataType.ensureNameNotExists(collectionDataTypeName);
+});
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts
index 6349efbd99c8..93c8c20c43fb 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts
@@ -10,6 +10,7 @@ setup('authenticate', async ({page}) => {
await umbracoUi.login.enterEmail(process.env.UMBRACO_USER_LOGIN);
await umbracoUi.login.enterPassword(process.env.UMBRACO_USER_PASSWORD);
await umbracoUi.login.clickLoginButton();
+ await page.waitForTimeout(5000);
await umbracoUi.login.goToSection(ConstantHelper.sections.settings);
await umbracoUi.page.context().storageState({path: STORAGE_STATE});
});
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.GetFolderMediaTypes.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.GetFolderMediaTypes.cs
new file mode 100644
index 000000000000..a86900e76672
--- /dev/null
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.GetFolderMediaTypes.cs
@@ -0,0 +1,84 @@
+using NUnit.Framework;
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Models;
+
+namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
+
+public partial class MediaTypeEditingServiceTests
+{
+ [Test]
+ public async Task Can_Get_Default_Folder_Media_Type()
+ {
+ var folderMediaTypes = await MediaTypeEditingService.GetFolderMediaTypes( 0, 100);
+ Assert.AreEqual(1, folderMediaTypes.Total);
+ Assert.AreEqual(Constants.Conventions.MediaTypes.Folder, folderMediaTypes.Items.First().Alias);
+ }
+
+ [Test]
+ public async Task Can_Yield_Multiple_Folder_Media_Types()
+ {
+ var imageMediaType = MediaTypeService.Get(Constants.Conventions.MediaTypes.Image);
+
+ var createModel = MediaTypeCreateModel("Test Media Type", "testMediaType");
+ createModel.Description = "This is the Test description";
+ createModel.Icon = "icon icon-something";
+ createModel.AllowedAsRoot = true;
+ createModel.Properties = [];
+ createModel.AllowedContentTypes = new[]
+ {
+ new ContentTypeSort { Alias = imageMediaType.Alias, Key = imageMediaType.Key }
+ };
+
+ await MediaTypeEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
+
+ var folderMediaTypes = await MediaTypeEditingService.GetFolderMediaTypes( 0, 100);
+ Assert.AreEqual(2, folderMediaTypes.Total);
+ Assert.Multiple(() =>
+ {
+ var aliases = folderMediaTypes.Items.Select(i => i.Alias).ToArray();
+ Assert.IsTrue(aliases.Contains(Constants.Conventions.MediaTypes.Folder));
+ Assert.IsTrue(aliases.Contains("testMediaType"));
+ });
+ }
+
+ [Test]
+ public async Task System_Folder_Media_Type_Is_Always_Included()
+ {
+ // update the system "Folder" media type so it does not pass the conventions for a "folder" media type
+ // - remove all allowed child content types
+ // - add an "umbracoFile" property
+ var systemFolderMediaType = MediaTypeService.Get(Constants.Conventions.MediaTypes.Folder)!;
+ var updateModel = MediaTypeUpdateModel(Constants.Conventions.MediaTypes.Folder, Constants.Conventions.MediaTypes.Folder);
+ updateModel.Properties = new[]
+ {
+ MediaTypePropertyTypeModel("Test Property", Constants.Conventions.Media.File)
+ };
+ updateModel.AllowedContentTypes = [];
+
+ var updateResult = await MediaTypeEditingService.UpdateAsync(systemFolderMediaType, updateModel, Constants.Security.SuperUserKey);
+ Assert.IsTrue(updateResult.Success);
+
+ // despite the system "Folder" media type no longer living up to the "folder" media type requirements,
+ // it should still be considered a "folder"
+ var folderMediaTypes = await MediaTypeEditingService.GetFolderMediaTypes( 0, 100);
+ Assert.AreEqual(1, folderMediaTypes.Total);
+ Assert.AreEqual(Constants.Conventions.MediaTypes.Folder, folderMediaTypes.Items.First().Alias);
+ }
+
+ [Test]
+ public async Task Folder_Media_Types_Must_Have_Allowed_Content_Types()
+ {
+ var createModel = MediaTypeCreateModel("Test Media Type", "testMediaType");
+ createModel.Description = "This is the Test description";
+ createModel.Icon = "icon icon-something";
+ createModel.AllowedAsRoot = true;
+ createModel.Properties = [];
+ createModel.AllowedContentTypes = [];
+
+ await MediaTypeEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
+
+ var folderMediaTypes = await MediaTypeEditingService.GetFolderMediaTypes( 0, 100);
+ Assert.AreEqual(1, folderMediaTypes.Total);
+ Assert.AreEqual(Constants.Conventions.MediaTypes.Folder, folderMediaTypes.Items.First().Alias);
+ }
+}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs
index a2ea9ad916d5..8b1b7cfc7866 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaTypeEditingServiceTests.Update.cs
@@ -1,6 +1,7 @@
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Services.OperationStatus;
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
@@ -113,4 +114,22 @@ public async Task Can_Edit_Properties()
Assert.AreEqual(2, mediaType.NoGroupPropertyTypes.Count());
}
+
+ [TestCase(Constants.Conventions.MediaTypes.File)]
+ [TestCase(Constants.Conventions.MediaTypes.Folder)]
+ [TestCase(Constants.Conventions.MediaTypes.Image)]
+ public async Task Cannot_Change_Alias_Of_System_Media_Type(string mediaTypeAlias)
+ {
+ var mediaType = MediaTypeService.Get(mediaTypeAlias);
+ Assert.IsNotNull(mediaType);
+
+ var updateModel = MediaTypeUpdateModel(mediaTypeAlias, $"{mediaTypeAlias}_updated");
+ var result = await MediaTypeEditingService.UpdateAsync(mediaType, updateModel, Constants.Security.SuperUserKey);
+
+ Assert.Multiple(() =>
+ {
+ Assert.IsFalse(result.Success);
+ Assert.AreEqual(ContentTypeOperationStatus.NotAllowed, result.Status);
+ });
+ }
}
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs
index 084efb2371bf..cd138283ebf1 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Migrations/AdvancedMigrationTests.cs
@@ -2,6 +2,7 @@
// See LICENSE for more details.
using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Moq;
using NUnit.Framework;
@@ -35,6 +36,7 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest
private IMigrationBuilder MigrationBuilder => GetRequiredService();
private IUmbracoDatabaseFactory UmbracoDatabaseFactory => GetRequiredService();
private IPublishedSnapshotService PublishedSnapshotService => GetRequiredService();
+ private IServiceScopeFactory ServiceScopeFactory => GetRequiredService();
private DistributedCache DistributedCache => GetRequiredService();
private IMigrationPlanExecutor MigrationPlanExecutor => new MigrationPlanExecutor(
CoreScopeProvider,
@@ -44,7 +46,8 @@ public class AdvancedMigrationTests : UmbracoIntegrationTest
UmbracoDatabaseFactory,
PublishedSnapshotService,
DistributedCache,
- Mock.Of());
+ Mock.Of(),
+ ServiceScopeFactory);
[Test]
public void CreateTableOfTDto()
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs
index 6c779a3c6e83..8fa1a658c13e 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/MediaTypeServiceTests.cs
@@ -1,10 +1,8 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
-using System.Collections.Generic;
-using System.Linq;
using NUnit.Framework;
-using Umbraco.Cms.Core.DependencyInjection;
+using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
@@ -222,6 +220,34 @@ public void Can_Copy_MediaType_To_New_Parent_By_Performing_Clone()
originalMediaType.PropertyGroups.First(x => x.Name.StartsWith("Media")).Id);
}
+ [TestCase(Constants.Conventions.MediaTypes.File)]
+ [TestCase(Constants.Conventions.MediaTypes.Folder)]
+ [TestCase(Constants.Conventions.MediaTypes.Image)]
+ public void Cannot_Delete_System_Media_Type(string mediaTypeAlias)
+ {
+ // Arrange
+ // Act
+ var mediaType = MediaTypeService.Get(mediaTypeAlias);
+ Assert.IsNotNull(mediaType);
+
+ // Assert
+ Assert.Throws(() => MediaTypeService.Delete(mediaType));
+ }
+
+ [TestCase(Constants.Conventions.MediaTypes.File)]
+ [TestCase(Constants.Conventions.MediaTypes.Folder)]
+ [TestCase(Constants.Conventions.MediaTypes.Image)]
+ public void Cannot_Change_Alias_Of_System_Media_Type(string mediaTypeAlias)
+ {
+ // Arrange
+ // Act
+ var mediaType = MediaTypeService.Get(mediaTypeAlias);
+ Assert.IsNotNull(mediaType);
+
+ // Assert
+ Assert.Throws(() => mediaType.Alias += "_updated");
+ }
+
public class ContentNotificationHandler :
INotificationHandler
{
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
index 2ffdc7c97658..b7ab21b0628d 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
@@ -148,6 +148,12 @@
ContentBlueprintEditingServiceTests.cs
+
+ MediaTypeEditingServiceTests.cs
+
+
+ MediaTypeEditingServiceTests.cs
+
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs
index 2c30dc6214cd..9bba7839e8f9 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Manifest/PackageManifestServiceTests.cs
@@ -1,10 +1,10 @@
-using Microsoft.Extensions.Options;
-using Moq;
+using Moq;
using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Manifest;
using Umbraco.Cms.Infrastructure.Manifest;
+using Umbraco.Cms.Tests.Common;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Manifest;
@@ -32,7 +32,10 @@ public void SetUp()
NoAppCache.Instance,
new IsolatedCaches(type => NoAppCache.Instance));
- _service = new PackageManifestService(new[] { _readerMock.Object }, appCaches, new OptionsWrapper(new PackageManifestSettings()));
+ _service = new PackageManifestService(
+ new[] { _readerMock.Object },
+ appCaches,
+ new TestOptionsMonitor(new RuntimeSettings { Mode = RuntimeMode.Production }));
}
[Test]
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs
index fd279b5b3a88..b70d03622796 100644
--- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Migrations/MigrationPlanTests.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
@@ -76,7 +77,7 @@ public void CanExecute()
loggerFactory,
migrationBuilder,
databaseFactory,
- Mock.Of(), distributedCache, Mock.Of());
+ Mock.Of(), distributedCache, Mock.Of(), Mock.Of());
var plan = new MigrationPlan("default")
.From(string.Empty)