diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index d3cf365c89d3..cf80f9ef7bf2 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -5,6 +5,10 @@ parameters: displayName: Run SQL Server Integration Tests type: boolean default: false + - name: sqlServerLinuxAcceptanceTests + displayName: Run SQL Server Linux Acceptance Tests + type: boolean + default: false - name: myGetDeploy displayName: Deploy to MyGet type: boolean @@ -559,10 +563,11 @@ stages: CONNECTIONSTRINGS__UMBRACODBDSN_PROVIDERNAME: Microsoft.Data.SqlClient strategy: matrix: - Linux: - vmImage: "ubuntu-latest" - SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD) - CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True" + ${{ if eq(parameters.sqlServerLinuxAcceptanceTests, True) }} : + Linux: + vmImage: "ubuntu-latest" + SA_PASSWORD: $(UMBRACO__CMS__UNATTENDED__UNATTENDEDUSERPASSWORD) + CONNECTIONSTRINGS__UMBRACODBDSN: "Server=(local);Database=Umbraco;User Id=sa;Password=$(SA_PASSWORD);TrustServerCertificate=True" Windows: vmImage: "windows-latest" pool: diff --git a/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs index 442525b1415b..84f70e91c49a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/DataType/DataTypeControllerBase.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType; [VersionedApiBackOfficeRoute(Constants.UdiEntityType.DataType)] [ApiExplorerSettings(GroupName = "Data Type")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentOrMediaOrContentTypes)] +[Authorize(Policy = AuthorizationPolicies.TreeAccessDocumentsOrMediaOrMembersOrContentTypes)] public abstract class DataTypeControllerBase : ManagementApiControllerBase { protected IActionResult DataTypeOperationStatusResult(DataTypeOperationStatus status) => diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs index c79e64e4f179..8f882a246407 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -81,6 +81,7 @@ void AddAllowedApplicationsPolicy(string policyName, params string[] allowedClai AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocuments, Constants.Applications.Content); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentsOrDocumentTypes, Constants.Applications.Content, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentOrMediaOrContentTypes, Constants.Applications.Content, Constants.Applications.Settings, Constants.Applications.Media); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentsOrMediaOrMembersOrContentTypes, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessDocumentTypes, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessLanguages, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaTypes, Constants.Applications.Settings); diff --git a/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs index 817cd9fa2e45..463dbbd5ab84 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/DocumentPresentationFactory.cs @@ -22,19 +22,22 @@ internal sealed class DocumentPresentationFactory : IDocumentPresentationFactory private readonly ITemplateService _templateService; private readonly IPublicAccessService _publicAccessService; private readonly TimeProvider _timeProvider; + private readonly IIdKeyMap _idKeyMap; public DocumentPresentationFactory( IUmbracoMapper umbracoMapper, IDocumentUrlFactory documentUrlFactory, ITemplateService templateService, IPublicAccessService publicAccessService, - TimeProvider timeProvider) + TimeProvider timeProvider, + IIdKeyMap idKeyMap) { _umbracoMapper = umbracoMapper; _documentUrlFactory = documentUrlFactory; _templateService = templateService; _publicAccessService = publicAccessService; _timeProvider = timeProvider; + _idKeyMap = idKeyMap; } public async Task CreateResponseModelAsync(IContent content) @@ -73,10 +76,14 @@ public async Task CreatePublishedResponseModelAs public DocumentItemResponseModel CreateItemResponseModel(IDocumentEntitySlim entity) { + Attempt parentKeyAttempt = _idKeyMap.GetKeyForId(entity.ParentId, UmbracoObjectTypes.Document); + var responseModel = new DocumentItemResponseModel { Id = entity.Key, - IsTrashed = entity.Trashed + IsTrashed = entity.Trashed, + Parent = parentKeyAttempt.Success ? new ReferenceByIdModel { Id = parentKeyAttempt.Result } : null, + HasChildren = entity.HasChildren, }; responseModel.IsProtected = _publicAccessService.IsProtected(entity.Path); diff --git a/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationFactory.cs b/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationFactory.cs index 7948e0d01936..f9b417028155 100644 --- a/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationFactory.cs +++ b/src/Umbraco.Cms.Api.Management/Factories/MediaPresentationFactory.cs @@ -1,10 +1,13 @@ -using Umbraco.Cms.Api.Management.ViewModels.Content; +using Umbraco.Cms.Api.Management.ViewModels; +using Umbraco.Cms.Api.Management.ViewModels.Content; using Umbraco.Cms.Api.Management.ViewModels.Media; using Umbraco.Cms.Api.Management.ViewModels.Media.Item; using Umbraco.Cms.Api.Management.ViewModels.MediaType; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Api.Management.Factories; @@ -12,13 +15,16 @@ internal sealed class MediaPresentationFactory : IMediaPresentationFactory { private readonly IUmbracoMapper _umbracoMapper; private readonly IMediaUrlFactory _mediaUrlFactory; + private readonly IIdKeyMap _idKeyMap; public MediaPresentationFactory( IUmbracoMapper umbracoMapper, - IMediaUrlFactory mediaUrlFactory) + IMediaUrlFactory mediaUrlFactory, + IIdKeyMap idKeyMap) { _umbracoMapper = umbracoMapper; _mediaUrlFactory = mediaUrlFactory; + _idKeyMap = idKeyMap; } public MediaResponseModel CreateResponseModel(IMedia media) @@ -32,10 +38,14 @@ public MediaResponseModel CreateResponseModel(IMedia media) public MediaItemResponseModel CreateItemResponseModel(IMediaEntitySlim entity) { + Attempt parentKeyAttempt = _idKeyMap.GetKeyForId(entity.ParentId, UmbracoObjectTypes.Media); + var responseModel = new MediaItemResponseModel { Id = entity.Key, - IsTrashed = entity.Trashed + IsTrashed = entity.Trashed, + Parent = parentKeyAttempt.Success ? new ReferenceByIdModel { Id = parentKeyAttempt.Result } : null, + HasChildren = entity.HasChildren, }; responseModel.MediaType = _umbracoMapper.Map(entity)!; diff --git a/src/Umbraco.Cms.Api.Management/OpenApi.json b/src/Umbraco.Cms.Api.Management/OpenApi.json index a57ae4553668..261279dc645b 100644 --- a/src/Umbraco.Cms.Api.Management/OpenApi.json +++ b/src/Umbraco.Cms.Api.Management/OpenApi.json @@ -36738,6 +36738,7 @@ "DocumentItemResponseModel": { "required": [ "documentType", + "hasChildren", "id", "isProtected", "isTrashed", @@ -36755,6 +36756,17 @@ "isProtected": { "type": "boolean" }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ], + "nullable": true + }, + "hasChildren": { + "type": "boolean" + }, "documentType": { "oneOf": [ { @@ -38895,6 +38907,7 @@ }, "MediaItemResponseModel": { "required": [ + "hasChildren", "id", "isTrashed", "mediaType", @@ -38909,6 +38922,17 @@ "isTrashed": { "type": "boolean" }, + "parent": { + "oneOf": [ + { + "$ref": "#/components/schemas/ReferenceByIdModel" + } + ], + "nullable": true + }, + "hasChildren": { + "type": "boolean" + }, "mediaType": { "oneOf": [ { diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs index 6dc8aa4d596f..ad7d991a00b1 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Document/Item/DocumentItemResponseModel.cs @@ -10,6 +10,10 @@ public class DocumentItemResponseModel : ItemResponseModelBase public bool IsProtected { get; set; } + public ReferenceByIdModel? Parent { get; set; } + + public bool HasChildren { get; set; } + public DocumentTypeReferenceResponseModel DocumentType { get; set; } = new(); public IEnumerable Variants { get; set; } = Enumerable.Empty(); diff --git a/src/Umbraco.Cms.Api.Management/ViewModels/Media/Item/MediaItemResponseModel.cs b/src/Umbraco.Cms.Api.Management/ViewModels/Media/Item/MediaItemResponseModel.cs index 2eb427060038..d342a163b85d 100644 --- a/src/Umbraco.Cms.Api.Management/ViewModels/Media/Item/MediaItemResponseModel.cs +++ b/src/Umbraco.Cms.Api.Management/ViewModels/Media/Item/MediaItemResponseModel.cs @@ -8,6 +8,10 @@ public class MediaItemResponseModel : ItemResponseModelBase { public bool IsTrashed { get; set; } + public ReferenceByIdModel? Parent { get; set; } + + public bool HasChildren { get; set; } + public MediaTypeReferenceResponseModel MediaType { get; set; } = new(); public IEnumerable Variants { get; set; } = Enumerable.Empty(); diff --git a/src/Umbraco.Core/Constants-Icons.cs b/src/Umbraco.Core/Constants-Icons.cs index 53501a012c90..cf2c263d83c7 100644 --- a/src/Umbraco.Core/Constants-Icons.cs +++ b/src/Umbraco.Core/Constants-Icons.cs @@ -67,7 +67,7 @@ public static class Icons /// /// System media audio icon. /// - public const string MediaAudio = "icon-sound-waves"; + public const string MediaAudio = "icon-audio-lines"; /// /// System media article icon @@ -77,7 +77,7 @@ public static class Icons /// /// System media vector icon. /// - public const string MediaVectorGraphics = "icon-picture"; + public const string MediaVectorGraphics = "icon-origami"; /// /// System media folder icon. diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/Breadcrumb.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/Breadcrumb.cshtml index 612c80948779..e609ac7c684a 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/Breadcrumb.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/Breadcrumb.cshtml @@ -1,16 +1,19 @@ +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedUrlProvider PublishedUrlProvider @* This snippet makes a breadcrumb of parents using an unordered HTML list. How it works: - - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back + - It uses the Ancestors method to get all parents and then generates links so the visitor can go back - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = Model?.Content.Ancestors().ToArray(); } +@{ var selection = Model?.Content.Ancestors(PublishedContentCache, DocumentNavigationQueryService).ToArray(); } @if (selection?.Length > 0) { diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/ListAncestorsFromCurrentPage.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/ListAncestorsFromCurrentPage.cshtml index a0056976666d..1ab6c7fb1d08 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/ListAncestorsFromCurrentPage.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/ListAncestorsFromCurrentPage.cshtml @@ -1,16 +1,19 @@ +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedUrlProvider PublishedUrlProvider @* This snippet makes a list of links to the of parents of the current page using an unordered HTML list. How it works: - - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back + - It uses the Ancestors method to get all parents and then generates links so the visitor can go back - Finally it outputs the name of the current page (without a link) *@ -@{ var selection = Model?.Content.Ancestors().ToArray(); } +@{ var selection = Model?.Content.Ancestors(PublishedContentCache, DocumentNavigationQueryService).ToArray(); } @if (selection?.Length > 0) { diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesFromCurrentPage.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesFromCurrentPage.cshtml index 2a88e4766047..39cd12665b51 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesFromCurrentPage.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesFromCurrentPage.cshtml @@ -1,8 +1,11 @@ -@using Umbraco.Cms.Core @using Umbraco.Cms.Core.Models.PublishedContent +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IVariationContextAccessor VariationContextAccessor +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider @* @@ -13,7 +16,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } +@{ var selection = Model?.Content.Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } @if (selection?.Length > 0) { diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByDate.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByDate.cshtml index 168e9b63acf0..9b6796096ca2 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByDate.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByDate.cshtml @@ -1,8 +1,11 @@ -@using Umbraco.Cms.Core @using Umbraco.Cms.Core.Models.PublishedContent +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IVariationContextAccessor VariationContextAccessor +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider @* @@ -14,7 +17,7 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderByDescending(x => x.CreateDate).ToArray(); } +@{ var selection = Model?.Content.Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService).Where(x => x.IsVisible(PublishedValueFallback)).OrderByDescending(x => x.CreateDate).ToArray(); } @if (selection?.Length > 0) { diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByName.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByName.cshtml index aed9085a76de..575ecae74d3c 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByName.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesOrderedByName.cshtml @@ -1,8 +1,11 @@ -@using Umbraco.Cms.Core @using Umbraco.Cms.Core.Models.PublishedContent +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IVariationContextAccessor VariationContextAccessor +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider @* @@ -14,14 +17,14 @@ - It then generates links so the visitor can go to each page *@ -@{ var selection = Model?.Content.Children.Where(x => x.IsVisible(PublishedValueFallback)).OrderBy(x => x.Name).ToArray(); } +@{ var selection = Model?.Content.Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService).Where(x => x.IsVisible(PublishedValueFallback)).OrderBy(x => x.Name).ToArray(); } @if (selection?.Length > 0) { } diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesWithDoctype.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesWithDoctype.cshtml index 95a65d0af902..9b7b24abc000 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesWithDoctype.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/ListChildPagesWithDoctype.cshtml @@ -1,9 +1,11 @@ -@using Umbraco.Cms.Core @using Umbraco.Cms.Core.Models.PublishedContent +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage @inject IVariationContextAccessor VariationContextAccessor +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider @* @@ -13,7 +15,7 @@ (You can find the alias of your Document Type by editing it in the Settings section) *@ -@{ var selection = Model?.Content.Children(VariationContextAccessor).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } +@{ var selection = Model?.Content.Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } @if (selection?.Length > 0) { diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/Navigation.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/Navigation.cshtml index 75397076702e..3b737ad9b616 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/Navigation.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/Navigation.cshtml @@ -1,8 +1,11 @@ -@using Umbraco.Cms.Core @using Umbraco.Cms.Core.Models.PublishedContent +@using Umbraco.Cms.Core.PublishedCache @using Umbraco.Cms.Core.Routing -@using Umbraco.Extensions +@using Umbraco.Cms.Core.Services.Navigation @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IVariationContextAccessor VariationContextAccessor +@inject IPublishedContentCache PublishedContentCache +@inject IDocumentNavigationQueryService DocumentNavigationQueryService @inject IPublishedValueFallback PublishedValueFallback @inject IPublishedUrlProvider PublishedUrlProvider @* @@ -11,7 +14,7 @@ It also highlights the current active page/section in the navigation with the CSS class "current". *@ -@{ var selection = Model?.Content.Root().Children.Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } +@{ var selection = Model?.Content.Root(PublishedContentCache, DocumentNavigationQueryService).Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService).Where(x => x.IsVisible(PublishedValueFallback)).ToArray(); } @if (selection?.Length > 0) { diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml index 20b31b6dcbf8..9f2c1c419382 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/SiteMap.cshtml @@ -17,7 +17,7 @@ - It uses a local method called Traverse() to select and display the markup and links. *@ -@{ var selection = Model?.Content.Root(); } +@{ var selection = Model?.Content.Root(PublishedContentCache, DocumentNavigationQueryService); }
@* Render the sitemap by passing the root node to the traverse method, below *@ @@ -26,13 +26,13 @@ @* Helper method to traverse through all descendants *@ @{ - void Traverse(IPublishedContent? node) + void Traverse(IPublishedContent node) { //Update the level to reflect how deep you want the sitemap to go const int maxLevelForSitemap = 4; @* Select visible children *@ - var selection = node? + var selection = node .Children(VariationContextAccessor, PublishedContentCache, DocumentNavigationQueryService) .Where(x => x.IsVisible(PublishedValueFallback) && x.Level <= maxLevelForSitemap) .ToArray(); diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs index 0e0ca72ed470..059ce2a2e9f0 100644 --- a/src/Umbraco.Core/IO/ViewHelper.cs +++ b/src/Umbraco.Core/IO/ViewHelper.cs @@ -91,7 +91,7 @@ public string CreateView(ITemplate t, bool overWrite = false) return t.Content; } - public string ViewPath(string alias) => _viewFileSystem.GetRelativePath(CultureInfo.InvariantCulture.TextInfo.ToTitleCase(alias.Replace(" ", string.Empty)) + ".cshtml"); + public string ViewPath(string alias) => _viewFileSystem.GetRelativePath(alias.Replace(" ", string.Empty) + ".cshtml"); private string SaveTemplateToFile(ITemplate template) { diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 08147a48a96a..473de72a767f 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2772,6 +2772,9 @@ public bool RecycleBinSmells() descendantCopy.CreatorId = userId; descendantCopy.WriterId = userId; + // since the repository relies on the dirty state to figure out whether it needs to update the sort order, we mark it dirty here + descendantCopy.SortOrder = descendantCopy.SortOrder; + // save and flush (see above) _documentRepository.Save(descendantCopy); diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index eac8aca1b550..b805f7763ac1 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1932,7 +1932,7 @@ string GridCollectionView(string collectionViewType) => EditorAlias = Constants.PropertyEditors.Aliases.RichText, EditorUiAlias = "Umb.PropertyEditorUi.Tiptap", DbType = "Ntext", - Configuration = "{\"extensions\": [\"Umb.Tiptap.Block\", \"Umb.Tiptap.Blockquote\", \"Umb.Tiptap.Bold\", \"Umb.Tiptap.CodeBlock\", \"Umb.Tiptap.Embed\", \"Umb.Tiptap.Figure\", \"Umb.Tiptap.Heading\", \"Umb.Tiptap.HorizontalRule\", \"Umb.Tiptap.Image\", \"Umb.Tiptap.Italic\", \"Umb.Tiptap.Link\", \"Umb.Tiptap.List\", \"Umb.Tiptap.MediaUpload\", \"Umb.Tiptap.Strike\", \"Umb.Tiptap.Subscript\", \"Umb.Tiptap.Superscript\", \"Umb.Tiptap.Table\", \"Umb.Tiptap.TextAlign\", \"Umb.Tiptap.Underline\"], \"maxImageSize\": 500, \"overlaySize\": \"medium\", \"toolbar\": [[[\"Umb.Tiptap.Toolbar.SourceEditor\"], [\"Umb.Tiptap.Toolbar.Bold\", \"Umb.Tiptap.Toolbar.Italic\", \"Umb.Tiptap.Toolbar.Underline\"], [\"Umb.Tiptap.Toolbar.TextAlignLeft\", \"Umb.Tiptap.Toolbar.TextAlignCenter\", \"Umb.Tiptap.Toolbar.TextAlignRight\"], [\"Umb.Tiptap.Toolbar.BulletList\", \"Umb.Tiptap.Toolbar.OrderedList\"], [\"Umb.Tiptap.Toolbar.Blockquote\", \"Umb.Tiptap.Toolbar.HorizontalRule\"], [\"Umb.Tiptap.Toolbar.Link\", \"Umb.Tiptap.Toolbar.Unlink\"], [\"Umb.Tiptap.Toolbar.MediaPicker\", \"Umb.Tiptap.Toolbar.EmbeddedMedia\"]]]}", + Configuration = "{\"extensions\": [\"Umb.Tiptap.Embed\", \"Umb.Tiptap.Link\", \"Umb.Tiptap.Figure\", \"Umb.Tiptap.Image\", \"Umb.Tiptap.Subscript\", \"Umb.Tiptap.Superscript\", \"Umb.Tiptap.Table\", \"Umb.Tiptap.Underline\", \"Umb.Tiptap.TextAlign\", \"Umb.Tiptap.MediaUpload\"], \"maxImageSize\": 500, \"overlaySize\": \"medium\", \"toolbar\": [[[\"Umb.Tiptap.Toolbar.SourceEditor\"], [\"Umb.Tiptap.Toolbar.Bold\", \"Umb.Tiptap.Toolbar.Italic\", \"Umb.Tiptap.Toolbar.Underline\"], [\"Umb.Tiptap.Toolbar.TextAlignLeft\", \"Umb.Tiptap.Toolbar.TextAlignCenter\", \"Umb.Tiptap.Toolbar.TextAlignRight\"], [\"Umb.Tiptap.Toolbar.BulletList\", \"Umb.Tiptap.Toolbar.OrderedList\"], [\"Umb.Tiptap.Toolbar.Blockquote\", \"Umb.Tiptap.Toolbar.HorizontalRule\"], [\"Umb.Tiptap.Toolbar.Link\", \"Umb.Tiptap.Toolbar.Unlink\"], [\"Umb.Tiptap.Toolbar.MediaPicker\", \"Umb.Tiptap.Toolbar.EmbeddedMedia\"]]]}", }); } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 5c11e512f1ba..2a80dde09af3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -916,9 +916,10 @@ protected override void PersistNewItem(IContent entity) NodeDto parent = GetParentNodeDto(entity.ParentId); var level = parent.Level + 1; - var sortOrderExists = SortorderExists(entity.ParentId, entity.SortOrder); + var calculateSortOrder = (entity is { HasIdentity: false, SortOrder: 0 } && entity.IsPropertyDirty(nameof(entity.SortOrder)) is false) // SortOrder was not updated from it's default value + || SortorderExists(entity.ParentId, entity.SortOrder); // if the sortorder of the entity already exists get a new one, else use the sortOrder of the entity - var sortOrder = sortOrderExists ? GetNewChildSortOrder(entity.ParentId, 0) : entity.SortOrder; + var sortOrder = calculateSortOrder ? GetNewChildSortOrder(entity.ParentId, 0) : entity.SortOrder; // persist the node dto NodeDto nodeDto = dto.ContentDto.NodeDto; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs index beda0418f271..5040d14bbe64 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/FileUploadPropertyValueEditor.cs @@ -87,7 +87,7 @@ public FileUploadPropertyValueEditor( FileUploadValue? editorModelValue = ParseFileUploadValue(editorValue.Value); // no change? - if (editorModelValue?.TemporaryFileId.HasValue is not true) + if (editorModelValue?.TemporaryFileId.HasValue is not true && string.IsNullOrEmpty(editorModelValue?.Src) is false) { return currentValue; } @@ -99,7 +99,7 @@ public FileUploadPropertyValueEditor( : null; // resetting the current value? - if (editorModelValue?.Src is null && currentPath.IsNullOrWhiteSpace() is false) + if (string.IsNullOrEmpty(editorModelValue?.Src) && currentPath.IsNullOrWhiteSpace() is false) { // delete the current file and clear the value of this property _mediaFileManager.FileSystem.DeleteFile(currentPath); diff --git a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs index b27f4a85b294..3a203e3ad906 100644 --- a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs +++ b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs @@ -53,6 +53,7 @@ public static class AuthorizationPolicies public const string TreeAccessMediaOrMediaTypes = nameof(TreeAccessMediaOrMediaTypes); public const string TreeAccessDictionaryOrTemplates = nameof(TreeAccessDictionaryOrTemplates); public const string TreeAccessDocumentOrMediaOrContentTypes = nameof(TreeAccessDocumentOrMediaOrContentTypes); + public const string TreeAccessDocumentsOrMediaOrMembersOrContentTypes = nameof(TreeAccessDocumentsOrMediaOrMembersOrContentTypes); public const string TreeAccessStylesheetsOrDocumentOrMediaOrMember = nameof(TreeAccessStylesheetsOrDocumentOrMediaOrMember); public const string TreeAccessMembersOrMemberTypes = nameof(TreeAccessMembersOrMemberTypes); diff --git a/src/Umbraco.Web.UI.Client/.vscode/settings.json b/src/Umbraco.Web.UI.Client/.vscode/settings.json index 2b3d645ee9f2..8fc1bce8e6c8 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/settings.json +++ b/src/Umbraco.Web.UI.Client/.vscode/settings.json @@ -4,6 +4,7 @@ "backoffice", "Backoffice", "combobox", + "contenteditable", "ctrls", "devs", "Dropcursor", diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 3a66e3a1d814..4d4a13cc8b01 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -29,8 +29,8 @@ "@types/diff": "^5.2.1", "@types/dompurify": "^3.0.5", "@types/uuid": "^10.0.0", - "@umbraco-ui/uui": "^1.11.0", - "@umbraco-ui/uui-css": "^1.11.0", + "@umbraco-ui/uui": "^1.12.1", + "@umbraco-ui/uui-css": "^1.12.1", "base64-js": "^1.5.1", "diff": "^5.2.0", "dompurify": "^3.1.6", @@ -4444,897 +4444,814 @@ "link": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.11.0.tgz", - "integrity": "sha512-1mX7adcpAZRswPA1p64kqE83Rg5cbZsYM/b/OyUcObaL2cIuBCVvjjuUjgkL2el993GptIzl05XVocdj1dDCeQ==", - "license": "MIT", - "dependencies": { - "@umbraco-ui/uui-action-bar": "1.11.0", - "@umbraco-ui/uui-avatar": "1.11.0", - "@umbraco-ui/uui-avatar-group": "1.11.0", - "@umbraco-ui/uui-badge": "1.11.0", - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-boolean-input": "1.11.0", - "@umbraco-ui/uui-box": "1.11.0", - "@umbraco-ui/uui-breadcrumbs": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-button-group": "1.11.0", - "@umbraco-ui/uui-button-inline-create": "1.11.0", - "@umbraco-ui/uui-card": "1.11.0", - "@umbraco-ui/uui-card-block-type": "1.11.0", - "@umbraco-ui/uui-card-content-node": "1.11.0", - "@umbraco-ui/uui-card-media": "1.11.0", - "@umbraco-ui/uui-card-user": "1.11.0", - "@umbraco-ui/uui-caret": "1.11.0", - "@umbraco-ui/uui-checkbox": "1.11.0", - "@umbraco-ui/uui-color-area": "1.11.0", - "@umbraco-ui/uui-color-picker": "1.11.0", - "@umbraco-ui/uui-color-slider": "1.11.0", - "@umbraco-ui/uui-color-swatch": "1.11.0", - "@umbraco-ui/uui-color-swatches": "1.11.0", - "@umbraco-ui/uui-combobox": "1.11.0", - "@umbraco-ui/uui-combobox-list": "1.11.0", - "@umbraco-ui/uui-css": "1.11.0", - "@umbraco-ui/uui-dialog": "1.11.0", - "@umbraco-ui/uui-dialog-layout": "1.11.0", - "@umbraco-ui/uui-file-dropzone": "1.11.0", - "@umbraco-ui/uui-file-preview": "1.11.0", - "@umbraco-ui/uui-form": "1.11.0", - "@umbraco-ui/uui-form-layout-item": "1.11.0", - "@umbraco-ui/uui-form-validation-message": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0", - "@umbraco-ui/uui-icon-registry": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0", - "@umbraco-ui/uui-input": "1.11.0", - "@umbraco-ui/uui-input-file": "1.11.0", - "@umbraco-ui/uui-input-lock": "1.11.0", - "@umbraco-ui/uui-input-password": "1.11.0", - "@umbraco-ui/uui-keyboard-shortcut": "1.11.0", - "@umbraco-ui/uui-label": "1.11.0", - "@umbraco-ui/uui-loader": "1.11.0", - "@umbraco-ui/uui-loader-bar": "1.11.0", - "@umbraco-ui/uui-loader-circle": "1.11.0", - "@umbraco-ui/uui-menu-item": "1.11.0", - "@umbraco-ui/uui-modal": "1.11.0", - "@umbraco-ui/uui-pagination": "1.11.0", - "@umbraco-ui/uui-popover": "1.11.0", - "@umbraco-ui/uui-popover-container": "1.11.0", - "@umbraco-ui/uui-progress-bar": "1.11.0", - "@umbraco-ui/uui-radio": "1.11.0", - "@umbraco-ui/uui-range-slider": "1.11.0", - "@umbraco-ui/uui-ref": "1.11.0", - "@umbraco-ui/uui-ref-list": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0", - "@umbraco-ui/uui-ref-node-data-type": "1.11.0", - "@umbraco-ui/uui-ref-node-document-type": "1.11.0", - "@umbraco-ui/uui-ref-node-form": "1.11.0", - "@umbraco-ui/uui-ref-node-member": "1.11.0", - "@umbraco-ui/uui-ref-node-package": "1.11.0", - "@umbraco-ui/uui-ref-node-user": "1.11.0", - "@umbraco-ui/uui-scroll-container": "1.11.0", - "@umbraco-ui/uui-select": "1.11.0", - "@umbraco-ui/uui-slider": "1.11.0", - "@umbraco-ui/uui-symbol-expand": "1.11.0", - "@umbraco-ui/uui-symbol-file": "1.11.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.11.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.11.0", - "@umbraco-ui/uui-symbol-folder": "1.11.0", - "@umbraco-ui/uui-symbol-lock": "1.11.0", - "@umbraco-ui/uui-symbol-more": "1.11.0", - "@umbraco-ui/uui-symbol-sort": "1.11.0", - "@umbraco-ui/uui-table": "1.11.0", - "@umbraco-ui/uui-tabs": "1.11.0", - "@umbraco-ui/uui-tag": "1.11.0", - "@umbraco-ui/uui-textarea": "1.11.0", - "@umbraco-ui/uui-toast-notification": "1.11.0", - "@umbraco-ui/uui-toast-notification-container": "1.11.0", - "@umbraco-ui/uui-toast-notification-layout": "1.11.0", - "@umbraco-ui/uui-toggle": "1.11.0", - "@umbraco-ui/uui-visually-hidden": "1.11.0" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.12.1.tgz", + "integrity": "sha512-wNx+ibT98rH8RxBBDfe3Y5YjClpxunTovuyTaY3BU6uVmC8UfxaW7qSggyeSvl+vthk30gg8u7pxzCuuv88xYw==", + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.12.1", + "@umbraco-ui/uui-avatar": "1.12.1", + "@umbraco-ui/uui-avatar-group": "1.12.1", + "@umbraco-ui/uui-badge": "1.12.1", + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-boolean-input": "1.12.1", + "@umbraco-ui/uui-box": "1.12.1", + "@umbraco-ui/uui-breadcrumbs": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-button-group": "1.12.1", + "@umbraco-ui/uui-button-inline-create": "1.12.1", + "@umbraco-ui/uui-card": "1.12.1", + "@umbraco-ui/uui-card-block-type": "1.12.1", + "@umbraco-ui/uui-card-content-node": "1.12.1", + "@umbraco-ui/uui-card-media": "1.12.1", + "@umbraco-ui/uui-card-user": "1.12.1", + "@umbraco-ui/uui-caret": "1.12.1", + "@umbraco-ui/uui-checkbox": "1.12.1", + "@umbraco-ui/uui-color-area": "1.12.1", + "@umbraco-ui/uui-color-picker": "1.12.1", + "@umbraco-ui/uui-color-slider": "1.12.1", + "@umbraco-ui/uui-color-swatch": "1.12.1", + "@umbraco-ui/uui-color-swatches": "1.12.1", + "@umbraco-ui/uui-combobox": "1.12.1", + "@umbraco-ui/uui-combobox-list": "1.12.1", + "@umbraco-ui/uui-css": "1.12.1", + "@umbraco-ui/uui-dialog": "1.12.1", + "@umbraco-ui/uui-dialog-layout": "1.12.1", + "@umbraco-ui/uui-file-dropzone": "1.12.1", + "@umbraco-ui/uui-file-preview": "1.12.1", + "@umbraco-ui/uui-form": "1.12.1", + "@umbraco-ui/uui-form-layout-item": "1.12.1", + "@umbraco-ui/uui-form-validation-message": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1", + "@umbraco-ui/uui-icon-registry": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1", + "@umbraco-ui/uui-input": "1.12.1", + "@umbraco-ui/uui-input-file": "1.12.1", + "@umbraco-ui/uui-input-lock": "1.12.1", + "@umbraco-ui/uui-input-password": "1.12.1", + "@umbraco-ui/uui-keyboard-shortcut": "1.12.1", + "@umbraco-ui/uui-label": "1.12.1", + "@umbraco-ui/uui-loader": "1.12.1", + "@umbraco-ui/uui-loader-bar": "1.12.1", + "@umbraco-ui/uui-loader-circle": "1.12.1", + "@umbraco-ui/uui-menu-item": "1.12.1", + "@umbraco-ui/uui-modal": "1.12.1", + "@umbraco-ui/uui-pagination": "1.12.1", + "@umbraco-ui/uui-popover": "1.12.1", + "@umbraco-ui/uui-popover-container": "1.12.1", + "@umbraco-ui/uui-progress-bar": "1.12.1", + "@umbraco-ui/uui-radio": "1.12.1", + "@umbraco-ui/uui-range-slider": "1.12.1", + "@umbraco-ui/uui-ref": "1.12.1", + "@umbraco-ui/uui-ref-list": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1", + "@umbraco-ui/uui-ref-node-data-type": "1.12.1", + "@umbraco-ui/uui-ref-node-document-type": "1.12.1", + "@umbraco-ui/uui-ref-node-form": "1.12.1", + "@umbraco-ui/uui-ref-node-member": "1.12.1", + "@umbraco-ui/uui-ref-node-package": "1.12.1", + "@umbraco-ui/uui-ref-node-user": "1.12.1", + "@umbraco-ui/uui-scroll-container": "1.12.1", + "@umbraco-ui/uui-select": "1.12.1", + "@umbraco-ui/uui-slider": "1.12.1", + "@umbraco-ui/uui-symbol-expand": "1.12.1", + "@umbraco-ui/uui-symbol-file": "1.12.1", + "@umbraco-ui/uui-symbol-file-dropzone": "1.12.1", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.1", + "@umbraco-ui/uui-symbol-folder": "1.12.1", + "@umbraco-ui/uui-symbol-lock": "1.12.1", + "@umbraco-ui/uui-symbol-more": "1.12.1", + "@umbraco-ui/uui-symbol-sort": "1.12.1", + "@umbraco-ui/uui-table": "1.12.1", + "@umbraco-ui/uui-tabs": "1.12.1", + "@umbraco-ui/uui-tag": "1.12.1", + "@umbraco-ui/uui-textarea": "1.12.1", + "@umbraco-ui/uui-toast-notification": "1.12.1", + "@umbraco-ui/uui-toast-notification-container": "1.12.1", + "@umbraco-ui/uui-toast-notification-layout": "1.12.1", + "@umbraco-ui/uui-toggle": "1.12.1", + "@umbraco-ui/uui-visually-hidden": "1.12.1" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.11.0.tgz", - "integrity": "sha512-lhWw7CiLL2FIXVOWgmAt8yeb625HYWXceMQMEwhlic4bp/jpVmrbYGuKl4SyubR4ws6ein4Uzzy1EWfT5K+kFQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.12.1.tgz", + "integrity": "sha512-MQBa8P8djO5SCy9BaAwHGHXBLe1MxEiGgAEln5FrkjAWJiV7x+9F8k/2MIuowmEJ/+6X38M9/bzDi3625TN7eA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button-group": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button-group": "1.12.1" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.11.0.tgz", - "integrity": "sha512-ixM8Kx9rE15iWYJgk28mEGeNvVDag/I8mZH/lceuq5Mm0EhUbG6gJGPkUSkDSNTnDRijkjwlF4oeCO+8nA+DRw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.12.1.tgz", + "integrity": "sha512-W3RW8pIwuhIiF/vy5GFdLQ3upmyT0nh/XD9yx0B7aOwZKYvNWo2pUAG0DeuK4Dw8NzyWew+jJx4rY+X49xDgEg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.11.0.tgz", - "integrity": "sha512-/edFijQFzOsNMBbhg8eu0imhDnLE4MSoC30o4dQ4bI3XCtGLfJh1BiOgA+TLUU1vH7D0NIvidzH49+OOIUrvMg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.12.1.tgz", + "integrity": "sha512-sRdXCtfy0h1U+OBljtDiBFzN41A7umnHQKrfgGdjXVVGLsNzxACROkenRKPNnfae/TVmfF39Bi0wBiTdJRE7Sw==", "dependencies": { - "@umbraco-ui/uui-avatar": "1.11.0", - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-avatar": "1.12.1", + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.11.0.tgz", - "integrity": "sha512-7VMZzUZ+CYaFhsCe8XS8VgadBhXZtJh0ilZ695YG9Q9IAbAVyeART59VwRzO/1kS0hfCj10DPEKp8IPMbePWEA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.12.1.tgz", + "integrity": "sha512-LlffQ5M9019QyZSkYRUW2ju4ZWXsVhFvesbS+zJC/devVxNXejkEfUW/yeROVAwrGWMbqQWvy2NkdcEI8oSKwg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.11.0.tgz", - "integrity": "sha512-w7HQDNtEt0qnu+psrwxvrdNxUT08qZ1fYygqH9yeKFyE5GMDvYlL1TWU696Lo72LTbTdSMm/ka9b2QBJv1ZJxA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.12.1.tgz", + "integrity": "sha512-0KMlkaiJxu9yIaMJPb/i16XkHkbiQYcDmflJ2BaGyZQyL1cZilyvsUofid31PsAFLHpUTOLAOWFsaB0lBHtu/Q==", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.11.0.tgz", - "integrity": "sha512-3r/lMYSrFzrw6EclCRjJADtf+1yAYPcz5QRQ4yD7WxLD/Kb338HRgQ50pMG5Jwq28cdDha4n7aNx7mGInrHD3g==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.12.1.tgz", + "integrity": "sha512-mqrRaWcpgHbx8ExiDcJs+ycyzKbTTRJb2drFK61jd1C4qIfwjOjWTng30ruDlNubwL2i8yryBHMMjsSPJ7xMNQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.11.0.tgz", - "integrity": "sha512-gYiERouKMpFy/n/6LDh9ckzWpUa2vBmCsWS41Gskct3WZNSVdApZ3g2yvE9ZoJoJB2Q26JfbKShuw0BaJkEFxg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.12.1.tgz", + "integrity": "sha512-A5aD7qVCgc21lMHCr81Q9kvxyLBBwZTUesRj/D4yJCwD98bI9ZQan4Sy/q+JzHntJwzCkDLBou3SVWgJquSI3w==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-css": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-css": "1.12.1" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.11.0.tgz", - "integrity": "sha512-wRTtuZAKb0z2Mi3P3wb1CcIO1ExnnFD8vCsHxiTEAjb2e2VzEaEwnnugHnr8chxlOKiTPyX8NtsBXDLTnL/TRA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.12.1.tgz", + "integrity": "sha512-dpEDnOVnOXXBFlHl9RUCXWZLFMgo641o1fUi5j27bCRRvfcflqu4/WmwmQcrqUPnKQntDgwzoQTa2Vj4MEL3Yw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.11.0.tgz", - "integrity": "sha512-/9B8Rsar9OE9NP84fXBzu5HkEIvXuEtmoaa37QQq9STgLyqrqRMxj6Mt47k69tQgh79HDNu+nwc8A5Gv+h+HHA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.12.1.tgz", + "integrity": "sha512-S4DgX4AdT/vTwtXS8/ALQVn7MIfGhcyk0oEhnHWyB1eHKpOBMXU3q+GZUjoYZ33wHQvEh4pIJCGkTfBtzGLIKg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.11.0.tgz", - "integrity": "sha512-TW2OioMnjyTCjJA6lJhoX80SyeEb/R2BK6Py82/ZCifnVQ2QFWZ6PtIcnqGT+b0x95xTvzc19f+z4N841wYc8g==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.12.1.tgz", + "integrity": "sha512-MMH9xju42Glee2htq2ZMAYgFZTksIs3aLLRusj9+l4Thiij8mkdoLVTCAWHPaEFEioy1AJyDxtemtcwayQHSJw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.11.0.tgz", - "integrity": "sha512-hoKR3sj5V4kzJ9qR0xe5q6giz41QmcPVQRP+qd90BjpxefezgnN2fud+RC59ZbhssAmep031b1pONRZyFr+6ow==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.12.1.tgz", + "integrity": "sha512-T1ZcB87wUhg4dDJ8/zDctvyyuodqIBJlLgq7BgjrZCukfyO68mkI1jvRaixL5V/K0EFNUGvmTsSbfE5jA7OisA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.11.0.tgz", - "integrity": "sha512-MIesvjoBVgSNo+2ManDIpLtWXwsO3emhsloQH+nMoyU/ryy/HZMe/p4HRx/leZmM17HG3KXm2j8GpLHie8bU+w==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.12.1.tgz", + "integrity": "sha512-1+QnOF03qYAj6lTF6Ens9Q922x5kDx/P/Rp3nfEdduWVv+opuzPM9bjkoRBVfb2Udd8Kt2aNFujwX7H5r2oWJg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.11.0.tgz", - "integrity": "sha512-kZeFGwSwjdD+M9HwzJ+1bScFCnS3AV36RzXDc6YklVPh63PKlt+wDmiIDd2I4+jHp8NC1buzUz/2dkmZVYOYrg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.12.1.tgz", + "integrity": "sha512-Tx5f3ucchl1+90KE+srWtz0iI/VR3nKa0mJxAZJElfwQ2yZF3flAFJB2V+aajjKEeBu67DK5Q+27yYo9mUn2gQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-card": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-card": "1.12.1" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.11.0.tgz", - "integrity": "sha512-iEzCVOpucAoCQnDYaGaq2k38zXUax+09gUypt907h0YPc6vRoTou5N5masvxZYyRYJrtWxv5kFs+MtLynREjGA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.12.1.tgz", + "integrity": "sha512-TJjUkzXR+wko4sSl6FVgsOSqBahYSToIqgoKsg8oFU11d5F/MiylJ1aBg0IXR8IY0pZWjXvWDUWrSmbsmeHtlg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-card": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-card": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.11.0.tgz", - "integrity": "sha512-uOdN0iu5OKsOtxhTSE8epuUMo2iXq6FEVqBPQBHAmAFELDFyNf2UBwnBxnrTuU6RJ0jbGuLTqQQM7Gv8vD6Kjg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.12.1.tgz", + "integrity": "sha512-Zi2H3mzOfjfczF97SPS3d2sp8YNNj7amq3F3tJZxT0UWN/RENzAoMfFniLnxxOja7wuIM2rxGLIkeDJB6U7/Iw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-card": "1.11.0", - "@umbraco-ui/uui-symbol-file": "1.11.0", - "@umbraco-ui/uui-symbol-folder": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-card": "1.12.1", + "@umbraco-ui/uui-symbol-file": "1.12.1", + "@umbraco-ui/uui-symbol-folder": "1.12.1" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.11.0.tgz", - "integrity": "sha512-/6No4e+eLqCmivNeCHlLfmChKb6F8asv9pgZdi6mUr44TOc44OGvvuF1vONslf9f4B2eKbRTFmFwGVIfWpjOAw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.12.1.tgz", + "integrity": "sha512-24r/t5y0nZwcMb6zJWKBXQ+PTtnoinyPTmbOAo+CBuR+E4K0fjO7VwGsKW2VndB0wzhEgLeEfN3X7MsQdAtkHg==", "dependencies": { - "@umbraco-ui/uui-avatar": "1.11.0", - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-card": "1.11.0" + "@umbraco-ui/uui-avatar": "1.12.1", + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-card": "1.12.1" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.11.0.tgz", - "integrity": "sha512-Lq+zBOMeobRvFPhEps03efcy+NFOm27w5jqwJ/4ad2TbEMLTBLdSose/3ZqPV4nvTPMlWButRIFo3Nrp+4jL/Q==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.12.1.tgz", + "integrity": "sha512-arVHbjy549nnTNA85rOoDYsPo8cZH2vhW8i6cQo5wfYcYHJDIkSGP6/jEyMSGAXb8akCgN1CUdW1yg0T2K2bNg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.11.0.tgz", - "integrity": "sha512-bOfJXJ5LMiGCMD37A3mzYjiGTIvzjREN2AhtqGLbwcrAgj662WVhw0aQobo2+iIwaMUIAvl3kNS8930XDeUe/A==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.12.1.tgz", + "integrity": "sha512-qCQJwAYI8wd1o4HzPkdCrZbsg1HwCk2lpf0tJHovgLeZ7wdJhszRS0DPk/KXHmbQNpTqtmDvjZWoJrcnxY7V3w==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-boolean-input": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-boolean-input": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.11.0.tgz", - "integrity": "sha512-R1fWHHk7BPilveIF7vPWECAHz/FPKIdvqllYu9f/oJ3RWm7DJtfcNI+Eb7hwkPR/Uj8ug7SkcL4ZvXOG30Ux4A==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.12.1.tgz", + "integrity": "sha512-PTY/qseL3QfSqx/5LQg+QqO0hEAy0loH+lYpsHQxoBSoE7tIZmo923WjJ0UJA6rzBtfc7WYe9JRPeI0iLtEOCA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-base": "1.12.1", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.11.0.tgz", - "integrity": "sha512-EHU2DXmET3ehRQMwkVtS+nyrfIm8c9cu01GDQR6GFzRNl3G7nUKKdK0LyRQUEm7bSXbWpwctGz6qzB9/MCuxBg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.12.1.tgz", + "integrity": "sha512-BCf+1t53RYRzfJ5SVBtaPJGT+3a+96IV1mXjpbnzG13SW2ecAXQUwnK7VeEFScrY1wy2f4QWDIsL/wVkiLxSMw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-popover-container": "1.11.0", + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-popover-container": "1.12.1", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.11.0.tgz", - "integrity": "sha512-E2mW4hvARy4C7ETZ4PUCgeUPgSvw4HEPX1CpOWl32vM85R4F/K/RdS6OsSP3GHO/8oBYPjlLfX8betMrf4+3+Q==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.12.1.tgz", + "integrity": "sha512-cUY5lD3E+Jl41we0IlFIqpts65/BoXuVq03+o4cjr2Mn2VgOKekxMi2IpFYRnDhLDf0WHAv6yjfIO0S7dR8+sw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.11.0.tgz", - "integrity": "sha512-BeCyW9FyVmjE2W8u3k5bPwkRUIVbudK2q9VTKmIcnkwsZz8wv6dDpFoFb92So8YSzMhdiVIRQ14fnphHwMHfWQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.12.1.tgz", + "integrity": "sha512-Zs0JgyolV5Dyo19lMQJDGxebi5Mv2eD7Ei8TnUUhs339SB3omHELpy47DRB2uknQFdZGa79hy63qfpwSvUkpBA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0", + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.11.0.tgz", - "integrity": "sha512-t+BKLHKlnFdSB/AB0vihqMl7EuIUI1M+m7q07E/or+BX7juV2H+sVAwWdYiOlCjpC5wqi1RAKh41tPWyslc/vQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.12.1.tgz", + "integrity": "sha512-PzPKTiUCx1ngL6i2NyAbZ5ze3fDOa+OY2XV7rGCuYPA91820CwWVHquD0fB2WHEjohL8F31zOo3aNohYDJxsdg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-color-swatch": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-color-swatch": "1.12.1" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.11.0.tgz", - "integrity": "sha512-Z+cfhxoK6/tGdErNc1rvrT9NDjuZPJ/SHAJlm83ziPvbWxTGVgjf75nqNZ3z6VW9EVWWJ0Fstz2VTzo4K0mcRA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.12.1.tgz", + "integrity": "sha512-3tb4SXEjWDu23ZGNtdDeoJVtDdaOJ/VPv8Vvh9u/6Qpdfw5k8W2Wbl98fYYsuh7AYmKOHSO4aIE3WQRmqWwvlA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-combobox-list": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0", - "@umbraco-ui/uui-popover-container": "1.11.0", - "@umbraco-ui/uui-scroll-container": "1.11.0", - "@umbraco-ui/uui-symbol-expand": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-combobox-list": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1", + "@umbraco-ui/uui-popover-container": "1.12.1", + "@umbraco-ui/uui-scroll-container": "1.12.1", + "@umbraco-ui/uui-symbol-expand": "1.12.1" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.11.0.tgz", - "integrity": "sha512-XV59sGG4NYZq6llWC3OqxxpR44Cavwfn+/7ee8kTBPmjWhzvS5XijDCGQxhrLcIK74L6OnqrfLcUgItPQUA3Dg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.12.1.tgz", + "integrity": "sha512-WWVfmbT/ewlaO9qYePDiPWTtdEyd6gqMyEWEunu4WAEX2fLJ0496cMZtYVNrj7xqZUkpnDoP218fc4tdtMqKaA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.11.0.tgz", - "integrity": "sha512-DpYKHmA4/te9gYUTLfLNgp0sotkq9TJQ9XkBzXJerwye+IzZdKhIsCWf/m5S6jf065MPjncEtwBgxDdvvB8DrQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.12.1.tgz", + "integrity": "sha512-cWdoJw3OjdZ5QUoXhUufp/8mdGkVJ4DiI7/NgPaU2GrMbo+c1Q2cx4ST2/K0Q7nY6qa4P4WCSLMoFGyFoOwLKQ==", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.11.0.tgz", - "integrity": "sha512-aEpitRE2an8YGm/s0QDfGW/0ccWlnqgA9DhrosZ7kxTElj7BVMQOGVh/nQKBjf+finOGThjvTCM33eksmgPaOw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.12.1.tgz", + "integrity": "sha512-ek16mmx4YSlsyTi+8VAmtsbY2Zg4503dAs1tIehtQhF8oqeKCB8iAILkvLghL94dhdcovRMXPU14PUR6Ex9Ugg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-css": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-css": "1.12.1" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.11.0.tgz", - "integrity": "sha512-z7ZTDonZ/mEJ6u/WH7De/NzT4IZ+zgqR0mJn4ypsf8T0ixB/r7aDHZG9cTP9hG4gnUag8VNbdashMCroDLSYNA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.12.1.tgz", + "integrity": "sha512-ZXI+TUbYxU9NDGwyxdLVaEzaYUyQV97UT3ZxRvrT5LT84ezdnmAy+C5gyvrcb4/tLTrINg/AnYR1GXq5b/FsRA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.11.0.tgz", - "integrity": "sha512-oV/SKvKuSze7eTbALCU0sCGmzMe8JgVQrrOPwWpepO/x2VHlWTNQbBQpsFmTOksR89Qx8NlK3Umo84i1RkeF1w==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.12.1.tgz", + "integrity": "sha512-gDOhW9GESI/2r+q9RlY5/1NLcsFA7FD63DFqVhTW64uywkhQ+/XE0sGcwvO74j1rQ+OJr85Mh1WnZEUpySdHlQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-symbol-file-dropzone": "1.12.1" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.11.0.tgz", - "integrity": "sha512-ZgJb3rdlKHo3iu9XZwy+elzhcBfZXb1LzoRIsLuanVHYeq/pbSXFtw8cJYJ3a65dnA6ryvGbY2m5TrWw39slMg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.12.1.tgz", + "integrity": "sha512-6I1WhmdJ0Y1IlhaHkCEiTPQZ8CwaJQT3NxspNSXCCg/tkjgiFdwIwWXNDOeRz4sIMdINuT9N63+DwURqOeRpdQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-symbol-file": "1.11.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.11.0", - "@umbraco-ui/uui-symbol-folder": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-symbol-file": "1.12.1", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.12.1", + "@umbraco-ui/uui-symbol-folder": "1.12.1" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.11.0.tgz", - "integrity": "sha512-+RqU/N8FUfbvmNPYCOyjS5e4H86dsT7h4A/2+NT2HmuyFObeXhCFMyp/60Kpfb6X7wJtnw1qa8go3zb8Gv5cpw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.12.1.tgz", + "integrity": "sha512-m9pRXUUFF0VsEKpJrFIbpjDEWea91+LhWKwP6UoZf71Ae3EXVJdmm7KcaDSAKeFHyIe9/HfoGJ03A6iUQUBmKA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.11.0.tgz", - "integrity": "sha512-o8V+S7mNoTV5mceCaTtY6+dFhzpJAxcR/e+1kN7yq6SfiabVjfW6EBqQYAnVc/hT9WfS3AUgO/8YFdr1CKOTqA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.12.1.tgz", + "integrity": "sha512-JZYaX196VeC5nRad4xc0+IFTEp2+bAOT4NRp/J4NVqdeu6kxA6BHoxbkSX2umjGVGODceiIy6g2X6dZKircvMQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-form-validation-message": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-form-validation-message": "1.12.1" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.11.0.tgz", - "integrity": "sha512-VxkPNQNPbMNMX/cPzrkekdGC7QUlyb9aH4feGe1RzD33hRc9FQufoTxS4gjSeX6yemjYu/78nqroBAMzIEmvUg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.12.1.tgz", + "integrity": "sha512-iu8ZzQwqjTj6S4hKb/q8AkWz/GVOTpqFiYPZN/7SOa9Gc6eDfSCtHq/PgAIs+F/13b8iSthOsmcBucYwA6CMAA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.11.0.tgz", - "integrity": "sha512-aH7tKlqfkMRU4+T8neSedU+YpHuFEhDe2ckHuqILw3iK1/j56Y0lUeoabkh1y/SWRZwydkkOjIhwDFIv48Ceag==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.12.1.tgz", + "integrity": "sha512-1leD+vxIh6/9LaU8ZXUKy2BUutGTWm8gBeGkDT2oGpinhKAx0KlHd0wgH0Rlrj/Mg3jv98sJpNCBlzsaphGP6w==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.11.0.tgz", - "integrity": "sha512-NbNDV35f1rSgKK2xFV/CPAdLPLhAFThilCPGraMY260WNIFwpcbP8n+PQ1dzNSec6xhIEMV4AC4Y5SvK/z54LA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.12.1.tgz", + "integrity": "sha512-micVlV7Fq8Nu9c1oDQjZkBJUnCyqEYFHifLjTayZK9GTiVl6aC33nvAk4SdBaTaS8pz01Uk6Ip8Drp39KRLB/Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.11.0.tgz", - "integrity": "sha512-WU5LRcjDFeGlr/Dq540IHLC1mMLgEkMJXjCNOb2d/7jLP3FHDs0T4qJGgzILYfeX7fDjQCnxkWVfaDmGGikSWA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.12.1.tgz", + "integrity": "sha512-GoSw6Hge+4E0vjQvdRAsUWjpylURYcmOQK6zBgKN/pEHeX1lhXJchi3ivGzD4cb+dVLlYDLztfcKRT2fSWn7pg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-icon-registry": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-icon-registry": "1.12.1" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.11.0.tgz", - "integrity": "sha512-DWe25cOCtYvRgqShL/UL4OnTRSbIZgTLp1JSdzLzSFxNm3PO2mAhYZuOMdGCjDkjv0G2lszmaqd7Ix8Xw+51ZA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.12.1.tgz", + "integrity": "sha512-joy0nUKui8LJzljDVeXb8LFLxGhpSAWvEg9EjsIin7I1bZemE3GtIt+q0+lN2Mqto6ixeCgOpqyEe4C+RnAGpQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.11.0.tgz", - "integrity": "sha512-u19lW5F7aiMN/D3wHhqJgqdreKaHJDoZ76A37nys2kItNWHvpoFbRrHkAaaN9RQVrl0rwmx3R6Sbs+IWFuTCJA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.12.1.tgz", + "integrity": "sha512-LgmPVzaRLFDati/Q9RgYA7c15B2DS8kIsdIbmJkxWbJk/Iaz8hgTQviB2QZ2SjP8g7K7ix0a/webS61J+wF4hw==", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.11.0", - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-file-dropzone": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0" + "@umbraco-ui/uui-action-bar": "1.12.1", + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-file-dropzone": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.11.0.tgz", - "integrity": "sha512-VCpLcFZ+OOeCubczsQsxrhqj3iPdq7o81YMxckd+BLiqU0O5nDxioSuZf5WeU7zttkTE64a0NYu0fKaRC7hLOA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.12.1.tgz", + "integrity": "sha512-ZovdaOZWucorfXDAe+c4DAtFTTPl8UyagXkBnTVTIW86T0kJ4f7HRTnHbG+jymEspwLq78FIkkk6oJYVXov8IQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0", - "@umbraco-ui/uui-input": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1", + "@umbraco-ui/uui-input": "1.12.1" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.11.0.tgz", - "integrity": "sha512-doilXxlrc8v6BUtXUhlrno2aQSzlApUw1B9nnG2TuFOxoJ3iynJV6p6CcwPNlNPEYzPeiHFOaizPeDaZWZYmRg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.12.1.tgz", + "integrity": "sha512-pZeXU1p8762x6nquBfUOF5fQNPk3U2FRxpfIsQvLA49x6TQ7L9y9mfrSzQ1mdwNoLyyNqaHKDr+VNqNwdCCEXg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0", - "@umbraco-ui/uui-input": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1", + "@umbraco-ui/uui-input": "1.12.1" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.11.0.tgz", - "integrity": "sha512-wRhfCnjjmZzs2gVoF1gZXNvIooPH8Qytr7UE6ijr6rDWbkDsltjhHocsPpcBAu1LUhqmqmlXDPHOOnc4sraL4A==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.12.1.tgz", + "integrity": "sha512-AbhVL5+1kCckdxiCrd6ncX3pS9XjPJxntVRtBiD6BESKrxSpt6V5MDJ7P/lnhHgHscalfjiwVrWAnbiR3zBeng==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.11.0.tgz", - "integrity": "sha512-xeVOm9gGyPERnmwjmBNiqsfHFU4ROn6wGIEg6bV/CRdz0sjOKBHMYjdH+hg00kRQjj8oYt52HK4dVos8lDDYZg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.12.1.tgz", + "integrity": "sha512-pQ8Z6oW6dBEG6xdKIGbGWXma16wmFDbOdmsL9L007Z2Bw4uJbW6yU4uXdi2YZC3AkIeb0AdIkLwYeKa/dwa69Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.11.0.tgz", - "integrity": "sha512-BoNCOFV+CXwMH/WEwVo5ADj6QXg1tIRPtzVtN3gZGTcDizbqp20171JtkeW3IvOpE6s9Gypn22bv1sUI+ZZOFA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.12.1.tgz", + "integrity": "sha512-FTonOLAqO95b8RDJDrdLcNsXS0pL+WKZYt8u6/DGV8YYg8fQymU5OA/YzNX4xi0OWOpVH7Ozto+F6j3/AzFOCA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.11.0.tgz", - "integrity": "sha512-WSIGG4Xlb/SuhnMmL0yd5ZaFUUdHR1UnZ6vv9ja5ORug88AnvPTNMY/53u2ilSh6NT0GCPXWFAbVgIZDn5KaFA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.12.1.tgz", + "integrity": "sha512-wd63QeW9wXQeEjwOID93ki/fPSlA2AqzdaCq0CvcVTR60FxJ7BUTxe6pPtP3aFC7ylcEllOof5R8pHn6L3wx2Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.11.0.tgz", - "integrity": "sha512-78xMkQVPUxSwEbvUIdg7L6lamliVKS+NVh+ZRGB+U3HG5t+kwXlcjgaQ4ebdkB7LgLvqrT41GEbXPsmk8hVKKQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.12.1.tgz", + "integrity": "sha512-itjl1UAvoSNs1jWzRLtcQLh56OgeJKoly4CnOmwkc/ftlPOmMLqVaUJt21vRMBaPtl0UIljkCbWDDGkPtexy2Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.11.0.tgz", - "integrity": "sha512-SMbTptyJdLCh03pSa1MflC0im6c7jaRdjb3p6exQ7cz++TdoLveJyOKAWaJ2TvaAShoaLOdlVHRD88sXcuj2Eg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.12.1.tgz", + "integrity": "sha512-w8kaWACizS0y1DP8xVQShcsZlZ+/OGoYQaFL7r6eN1YqSDDkEV+LkL8p/Wqrjxz1EmnTaYrdj8oqcqFDz2Gc/g==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-loader-bar": "1.11.0", - "@umbraco-ui/uui-symbol-expand": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-loader-bar": "1.12.1", + "@umbraco-ui/uui-symbol-expand": "1.12.1" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.11.0.tgz", - "integrity": "sha512-rNq8lhzKj4bk4EMgAIlnHcaQX0W7kQhHWBeJahvLL6jNMmiMGtN/ZtE0efG5tx1r4dixTPbiXXGAl8qMqgTIig==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.12.1.tgz", + "integrity": "sha512-Yfcf/ybcYAlYOd7NwYzH44hX8wM0hE5L3cypmstT/wI0f0n3kGk7AP89dBROPcctBwap6mycSspVc9FfVJzhfg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.11.0.tgz", - "integrity": "sha512-aQf9MH4BlBbR9r+u4jbknuivhXPrwn65YjLkO3gDDfVfeSSu+ZsrNxReUVnVehF+bP55htcxgwC/lKDJldHVEQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.12.1.tgz", + "integrity": "sha512-3GDh7WXkqcqgiwfDJiBUgm1btPmxOzEETeuzpTx8H9jaK8d5CHTQ8+uM8VIXxFCmKtUyA8JLuvS4w2GvpXGK4w==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-button-group": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-button-group": "1.12.1" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.11.0.tgz", - "integrity": "sha512-ZHjkuJ1z8P/zLFeBf8LB8+c/JXm6YK5SORVnZfIlF8MZSDLanFlST62uOT7dcop96yihI/zIr7O5vO8OEw44bw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.12.1.tgz", + "integrity": "sha512-XwvT3zieIJtBjK5Y3ithWCYH7b1jYEhmXI4HhHsakoUvmptaWM37c9ju2MGwghlfkhJg4DjBDI6xHQPopXbGpQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.11.0.tgz", - "integrity": "sha512-i90xXibf8BfP4Yd5Bp4wOfjnFEWQ2Qmn9vnDOWcxmsM9q7NQFx7m4287jJCMtfz2DUbj0VIFJlA2rffWnnSJzw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.12.1.tgz", + "integrity": "sha512-coNy6smdCLzPMgOD1IGylkT7Dj7X9DhA0nA5R0uSLUOiYgFi3PE4ziaksxIRjr6iVl+MgvzAB3pqyHKB1srv6g==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.11.0.tgz", - "integrity": "sha512-ZTRlebLZV19wvNS5TtX+Ku/1cXgAXBR9anYydx/+e2sXQeotwsak74vHqVgNYTzFqD+8EuRlwYJOI4kMer8COw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.12.1.tgz", + "integrity": "sha512-6WQd75DV5btJjYlybzMUkCUrapD9S/wJAraKVS0c5UvmfUs+pmMZEVnx6TypO4dllk6rHCOQQ79SuyZRft6NCA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.11.0.tgz", - "integrity": "sha512-s2KhChBWMlpUThSAm7HGPcbCFXJ7vQTTgSw1e+VED/p/xwKhMrcMiwGX1s4fRTXt4tnCm8AcbMSkhfrW4DW8IA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.12.1.tgz", + "integrity": "sha512-H28rLbALI8zdDlW8l/8TUQm8E4kiHkNlKOuz98WSYW+J0jl6gyiTOUWN6N4p8RsE9rHTS7d//IisOioFTErphQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.11.0.tgz", - "integrity": "sha512-ReU+Xh8VEH9L+ap4Zwo4+TFWDodoiU5iNkkM0NwbHMz/PLiBE0tVKD5wgppkJKnTRxDxS3MG98C+3DOvXqO2ZQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.12.1.tgz", + "integrity": "sha512-0D0vlz0chs4rvwdWCd45Y8PI7aDRCYZ6/3IN48oBhW4LQsU49aQi/bTyXurcTdaxKirf7DQmlNAtuyRRjw4WEg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.11.0.tgz", - "integrity": "sha512-gAtI3/FgcUmmUPSNY9HMGnlMSby9PrcZ1hJRFmv+b86Ducc+4ljmsro97noTexYG1zttDPMkvYGFqOeE5bAeDQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.12.1.tgz", + "integrity": "sha512-IdaajH2ajYPxQCLYVV+XfzlrF6R+kz8DLDeiS5cI5vgsF+bs1GmVFw2WzF1m2DFSUeuGSTLQcLWQbQsjHgGGnQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.11.0.tgz", - "integrity": "sha512-c0DLRyNs/sRKPqmnjY6QAfuPa8+etQpXK683gJEn5R4zwcJGGPFzRf6BD9nIcecAAnbL+MFd6cgCBZWlDq/BJA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.12.1.tgz", + "integrity": "sha512-MQUSeGMvM4e1Pzka4CdJOBsA8AMIHGuXX2QGE1uJj3u3EEn1WPQToD2YTSNB0Cxikfi08BEZT5GsbJI/PzwFmw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.11.0.tgz", - "integrity": "sha512-/+kpfFBb1su5/7egIAHQfeCm3+VQuMrwt07evovAeAM6YAdZsEcv8l2B0V09uKIcJJn/eJOfVVWlqWqi+qQazg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.12.1.tgz", + "integrity": "sha512-ZKkiAOT0oE8E1/BXp8FCavyAooJe5hYYmUSALV795SB4OHaVSIL8Kh6MUFNzmtUzs0qC5+cCVtef67KJe6kuEA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0", - "@umbraco-ui/uui-ref": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1", + "@umbraco-ui/uui-ref": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.11.0.tgz", - "integrity": "sha512-MED2t6TvjNgzLhV2aaWf/WJ6qA5fhWgFC11hCfEDdjqzhot7TrL4yI/YRDaEJXcYjb5rivod+c346ejSL9+Eog==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.12.1.tgz", + "integrity": "sha512-wY0OvqYSfVuuMYLVV1WKpITZ7SVZ+7RZ7DGOar3cJha6f3/e56J/ylunfTu1T+pxd/KIh8WNWZx3vVgWRFIm8Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.11.0.tgz", - "integrity": "sha512-S2kzH14m508FBkYalKsWEPLT2xShqryxuSs6caiYAi3cXm5MJq04phvRxl9Yo7h74PESojmZWHjRquPfCLEHog==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.12.1.tgz", + "integrity": "sha512-EmC7x7SO2Q1uYhIlESAX8QtxEbPIkBwDPcCr8l5bQ/Y/0uXvBOja8tceIt19xe954KjeqcRs6AGTWsetv4UoMA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.11.0.tgz", - "integrity": "sha512-S1RobwV2O69eyw5sDRrJJDcFNF49hfZ/UcsluK9snPBe080Hzcqjl8bp+6AnH5NyicVwwDW43s6KImXhlIhtVw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.12.1.tgz", + "integrity": "sha512-ovxnbkmGPeCYMKb8aDhH3yafTtkYEwsz9eGxPvoDlioTw/coRVo4VF0dndKzxIqVBw/BnNotnFhGWt1l5k47zQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.11.0.tgz", - "integrity": "sha512-rFqPLY2xnFNFaGgPvneYHapLbnmNhUBaGYnSBe8GJkywz1YFBfdJKj7OftKiqMVWidNz32fejGEUouj9zztxyw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.12.1.tgz", + "integrity": "sha512-iGO5qTr/8kAFcZItYAJGF7CES5SN7oBMw+MDVcZG/JgBmxogSW0wDlBM9u6FPMDnum+bDG+YpUbJpDN+VA0/pQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.11.0.tgz", - "integrity": "sha512-ykakG0czZnDdCMy5bRawizwYTu4J267vM1bJrfUa22+hSMKGMy/o4oKS+aKQ2Rh5eUlfBq80iylLDhn4rdmJ6A==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.12.1.tgz", + "integrity": "sha512-KWI2nKHUOHFqQzjYc1qextxKrczHJrzKCxGOnHU4gmqnJxGwjnWmx6XDndlBE8bBeggmC+nUt+RAE8jSfo3xmQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.11.0.tgz", - "integrity": "sha512-mrvjf+0usJmJRtTwg90bvLZvftBLG6IQPUxPqWEN7cYbwnDnT0GDn/5qA8Yx9+eND+xMU/I3Dvke9XOyLXfT9Q==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.12.1.tgz", + "integrity": "sha512-eO8aegyXUmFlxyvJFacM5lYkmP8eyroXmvbChoX9pfgkyONGKYLIGvpAvPFrL8447xVQdsWXCPMu0aTpiMqdAw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-ref-node": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-ref-node": "1.12.1" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.11.0.tgz", - "integrity": "sha512-e+8Fnc2rFtRdv52DpZW0UC9CnxzhXmIqRldYjTpbaL6Xjg9qNSdeW5AvJNk+fgufL6LJOO6NUXs6ixTp8eiOIA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.12.1.tgz", + "integrity": "sha512-aeAvzUwjGtGIxNdixR3uTgIyzhattAnXqb7n/iB4N8f6NW2ZyLAoDM79RcTa6k7tDF0zEdQjlAZfjKlHAzCfpg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.11.0.tgz", - "integrity": "sha512-slTOIvJZMMtCnVEhBVjAs1MPQBb1BAAa6R+DOoslC4aqA1yEgXWQmFu0xVZqiN0NTz3kqEF5zfexumVJ5f79LQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.12.1.tgz", + "integrity": "sha512-XvxMzKqwytjZr8wZ6PsT9fASMnXXC2GBS3Zlf/fbTzPoYFMxvprsVMe2MXeXoS8wXmQ/ojcsiWB3Gv0mgZvLUg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.11.0.tgz", - "integrity": "sha512-sxWZCvznmTkpJ+VyoIjMRsVQuYC2SMnTWFd+7xrg3pk5SRySNxhZhyQUyf5jI1hAzrW9ATySDZlaRYCOMsC7uA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.12.1.tgz", + "integrity": "sha512-LWMrH+3aX9cSNGLN/MhebrRCUdXxMkXUleXmg+M6LKvLfRiyN9PPJHBtRzrxS6DARj69BwnrViNVVu4toZXTYw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.11.0.tgz", - "integrity": "sha512-bFGp9Dhp8heBfNnu3ozw9DOIfwjkVcKNfHLSts6wg+J3vLW4x0y9jLfxSyvArQQUcUHKsgOzEHoNw6igYDpDJw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.12.1.tgz", + "integrity": "sha512-NlT5PtYVx/GwiDRcUX519mC9DEkSPJNDCSU50RwcRaXI1XvvIUq0Ds7BO4lFUeArnRdb13Vp4jV+Lh1Pmohvwg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.11.0.tgz", - "integrity": "sha512-AK411VsceFqOAGtvlK2VcyTqwPbYVdqJkXbTbsSxYVhIB2jMVISppwlefqerx4zbPASBp4aeIN54PZWN+Y3dfw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.12.1.tgz", + "integrity": "sha512-ST/wi+kwKftTLl9CEfYem3bAOFdaxiLN2soB4jWYUEkQL7uG1A+1oL0/6w8myq8PL210PO65khSuW0wCwVpolg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.11.0.tgz", - "integrity": "sha512-Tma0hziyVM3ZXUduL97i8s3zs5JjbZi9lbydPx7foL/vAhEdP7fov8OXF1kMBhYIEieT11td/9ARxKlDOaLojQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.12.1.tgz", + "integrity": "sha512-3XpIC2GPkkTKg6XzvlTV7XUahK3+DZ/F3PdUXk7LSpnxowxU/5hDDr8ESPj9jKaBe1OcDH/bc0fsRJKOtkmBNQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.11.0.tgz", - "integrity": "sha512-22JNF2zs9iumu5JTsn6WmvyMqOwjrZ5/tfeL8+4ZnrxWM5CmJ7neKTm5BHoJyj0oM1wML2NWAc4McbWNOXktrg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.12.1.tgz", + "integrity": "sha512-TXYGygPFzLT4f/8iH1X5fKn7GrpagLcYsPDIFkEH1dOAoQWR0gu+V2TVoKfXkzzdxZ68knB8lbPeuXi4StUXSA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.11.0.tgz", - "integrity": "sha512-NcQQupEQASwp8pyxVFG6v7rCvNAbgtE2R9IDlLl5yC/k3449TZ/NiEgMaSlmNhexBEc4SCoTMD9IuaEBo4vmZg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.12.1.tgz", + "integrity": "sha512-EUztWYMcWUiFTT3KRe7/qRLxOWu5sErNSVb+HRAYak3TOIXP/A0a7H09SRWTPAKqIjrmWo26es+lasCOC1rwEQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.11.0.tgz", - "integrity": "sha512-1PsxVXj5zT3vXOcb+LP6/bgfGOt0aUmIoAGtV6mO/QHb1XPmOB07xrRzkk7CX+VixOCIdkTGYNU/CFjPJwLsow==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.12.1.tgz", + "integrity": "sha512-2tW8u20OM0wS3ZbPIlyVeHYR6mG1KAOC47CxSaCs59QIIw2PVBZgY7YVVP6bSUf4kttNkPt826gf7lW9ZEBrCg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.11.0.tgz", - "integrity": "sha512-72OwXzXAm9XXLB/+qGhtl7IRzrq/2uDdMFG93EMJs0NM3MU0EM0Ild7MuIAPecGiCGjBYn/iyZmWhYMDhS/KOA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.12.1.tgz", + "integrity": "sha512-JrKpcSGizg20JUBKXEoMlDhfxpUFKp+JM9wldnm7JCyfee1JpMAQYDTF/Nw0Qsj5i60xqa7EAggZc38zQuDK6Q==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.11.0.tgz", - "integrity": "sha512-Y+PQc77PvmVOGAaPGRTYrtLI3MCV/BqE9hl0f+yGZYK/C97r3ogGQxMguU5zThf49EOEL3VmB/WWS/HEFblsjA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.12.1.tgz", + "integrity": "sha512-HXQa9X1GahHOtuHA2BPRFlqblS8PXuhkDJwFMsXTxShgMnv0Wq3pEp7gh791Kk/XAsOWXozZgRPxWeB9HfAaRg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.11.0.tgz", - "integrity": "sha512-AXKMARK9WtyuU9T72LGprhBQXpYKw4rWGoGQwUjRk4lwdQD8WKeY3kfIIcaeabBiK5FPnZaEoCpxIkmPt77n2w==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.12.1.tgz", + "integrity": "sha512-rTsJnhP1jgDku5R4gIQF0GcLXTqqYLHRRSIjP+m8WEK18EvQIIER4iRjt6SC1Iv/KVaWB6M4G3OXq3RfQqvknA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.11.0.tgz", - "integrity": "sha512-IyB1qao2G3T5UNBj3Kw9EL7ikjAp8COvHVH8eTD+fjx1PbrNJmDl6utTV6tpysxLkT7UQ3o6QtjxstDtlUSqsg==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.12.1.tgz", + "integrity": "sha512-UIQjeCm2jWJJSbpDZ5BKEQT6vh9vEpio6HVupH9KsF5lNq5JwCFsK29g+xCbDPT/tzbLFrSZiVGebQaJ9OrPkA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-popover-container": "1.11.0", - "@umbraco-ui/uui-symbol-more": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-popover-container": "1.12.1", + "@umbraco-ui/uui-symbol-more": "1.12.1" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.11.0.tgz", - "integrity": "sha512-TGMkL7J+PPOq0dZiXnj5Y7f6+c/IJl71I2cme75cE/SkzoI01hr1KvEEThHT83yn64PPqews8ZCh1fKwmI1tmw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.12.1.tgz", + "integrity": "sha512-YHhAgaQLCMI0NXmmXXtyTk+mRtHCQ/+RcLkncl92RaTL8jHqFmkFQ/mm2MTYKenzF1BjwtTDHop++KEoQ6aJgg==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.11.0.tgz", - "integrity": "sha512-g4ciGte7YgHJkzhkLPn4xiGfjHXFbUWa86S4bg3WricucdF20EReLRc6I2jW7mo8lL+h+y8wLcIIQ8CquscLsQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.12.1.tgz", + "integrity": "sha512-1es3dbrcl8dcGyHNSUVDvOlLsZtPCKAQubMF5qXj5uzkeO1vnVC8L4TgDchkncCLhI1Zi+Ur1dsZ7ZOr4KdW4w==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.11.0.tgz", - "integrity": "sha512-5Mhhwn5z/IdlO3iuMMM8HYlDXg9GM23NxCykDcNGpGxMW0TeMFNLNxsBqm+5fOsNYjL2vhv3utPZyeE57ulyQA==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.12.1.tgz", + "integrity": "sha512-jNUMISZKlxWxUW57CnFSpk69B/m3Pq6Z+mr28YEAmk1kIfRur5DpqZOeO+jQacT0aZY1ua2ETJ9R5ZDi5XMuZA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-button": "1.11.0", - "@umbraco-ui/uui-css": "1.11.0", - "@umbraco-ui/uui-icon": "1.11.0", - "@umbraco-ui/uui-icon-registry-essential": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-button": "1.12.1", + "@umbraco-ui/uui-css": "1.12.1", + "@umbraco-ui/uui-icon": "1.12.1", + "@umbraco-ui/uui-icon-registry-essential": "1.12.1" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.11.0.tgz", - "integrity": "sha512-Y0LunmaTU/06i6mZF/RmopCDvsZMbgYlayJ3K7w6qkqXeJCnLg9cWHQSmOvIz9DJPO84NOcoYCwsLo4DRYa8WQ==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.12.1.tgz", + "integrity": "sha512-LrjIFADbfb3C1TbUieo0haP1BCArcGcraZ6XcGa9YVVfB/yNKQPOz2M6U0vv14WulOBPeInHExaCV2GNkItEUw==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-toast-notification": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-toast-notification": "1.12.1" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.11.0.tgz", - "integrity": "sha512-lYuYhtgnO4ELs+qxc2bt6JPBdm+RYhcujMTpx8sSgCYPkHiwxnZt9WEfQQJe4wcwNyuGyMTcwn2d6BKMYgqP9g==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.12.1.tgz", + "integrity": "sha512-dhpCnbxdgKEO54yLDn6FJtupmRaSoZSRJeed9y6E344swEkDc8/frrt/nm0wwSjV022rnejH2SYjkRfYl2qazA==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-css": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-css": "1.12.1" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.11.0.tgz", - "integrity": "sha512-ZWafhMLnR/Z55U4Nw2mUYiPOWrIcSYS4Oay388ZuEKZmfQ0iwGYGSBo4awn3OeY/mVoY88QY6R2siRq9jABKig==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.12.1.tgz", + "integrity": "sha512-//gnh+j6SmFLJ5B/zFZEu8aUBnR5dflz/gvwK5SQclGsoG1fCCEltIof24E2nEIqb8/fRQaT5ZTla2TpMXWV3g==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0", - "@umbraco-ui/uui-boolean-input": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1", + "@umbraco-ui/uui-boolean-input": "1.12.1" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.11.0.tgz", - "integrity": "sha512-IxZwVLvX311+iupaupA36C6Ea3Aox/KAh/C5hE81qN+fNI/A8CZxr4OHHEvnQj4VcL0gTG0qt4PbxSR4hRfxmw==", - "license": "MIT", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.12.1.tgz", + "integrity": "sha512-wXbmn7hFy7U/WZSiZjcI+UdgvogLTN6+iDd8pioW2ehy0+2nSNVJIbkoKHS+h/TrpKiJADjT8205WFJBIriFcQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.11.0" + "@umbraco-ui/uui-base": "1.12.1" } }, "node_modules/@ungap/structured-clone": { @@ -7047,8 +6964,7 @@ "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" }, "node_modules/comma-separated-tokens": { "version": "2.0.3", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 1c7df1a50b34..7ddbb37fd22b 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -215,8 +215,8 @@ "@types/diff": "^5.2.1", "@types/dompurify": "^3.0.5", "@types/uuid": "^10.0.0", - "@umbraco-ui/uui": "^1.11.0", - "@umbraco-ui/uui-css": "^1.11.0", + "@umbraco-ui/uui": "^1.12.1", + "@umbraco-ui/uui-css": "^1.12.1", "base64-js": "^1.5.1", "diff": "^5.2.0", "dompurify": "^3.1.6", diff --git a/src/Umbraco.Web.UI.Client/src/external/router-slot/util/history.ts b/src/Umbraco.Web.UI.Client/src/external/router-slot/util/history.ts index 7ee34d74d045..5692ccb49ccf 100644 --- a/src/Umbraco.Web.UI.Client/src/external/router-slot/util/history.ts +++ b/src/Umbraco.Web.UI.Client/src/external/router-slot/util/history.ts @@ -30,7 +30,8 @@ export function ensureHistoryEvents() { // want the popstate event to bubble up before the changestate event is dispatched. window.addEventListener('popstate', (e: PopStateEvent) => { // Check if the state should be allowed to change - if (shouldCancelChangeState({ eventName: 'popstate' })) { + // [NL] I injected the url property here, cause we need that when URL is changed by the browser back/forth button. + if (shouldCancelChangeState({ url: window.location.pathname, eventName: 'popstate' })) { e.preventDefault(); e.stopPropagation(); return; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts index eec1070db2af..9ca00d697097 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/data-type/data-type.data.ts @@ -854,16 +854,6 @@ export const data: Array = [ ], }, { alias: 'orderBy', value: 'updateDate' }, - { - alias: 'bulkActionPermissions', - value: { - allowBulkPublish: true, - allowBulkUnpublish: false, - allowBulkCopy: true, - allowBulkMove: false, - allowBulkDelete: true, - }, - }, { alias: 'layouts', value: [ @@ -898,16 +888,6 @@ export const data: Array = [ ], }, { alias: 'orderBy', value: 'updateDate' }, - { - alias: 'bulkActionPermissions', - value: { - allowBulkPublish: false, - allowBulkUnpublish: false, - allowBulkCopy: true, - allowBulkMove: true, - allowBulkDelete: true, - }, - }, { alias: 'layouts', value: [ diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts index 0145765978bc..e6a54130b64f 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document-type/document-type.data.ts @@ -1825,4 +1825,78 @@ Search for **dt-richTextEditorTinyMce** in the codebase to find the configuratio keepLatestVersionPerDayForDays: null, }, }, + { + allowedTemplates: [], + defaultTemplate: null, + id: 'block-editors-document-type-id', + alias: 'blogPost', + name: 'Block Editors document type', + description: null, + icon: 'icon-item-arrangement', + allowedAsRoot: true, + variesByCulture: true, + variesBySegment: false, + isElement: false, + hasChildren: false, + parent: null, + isFolder: false, + properties: [ + { + id: '18', + container: { id: 'content-group-key' }, + alias: 'blockList', + name: 'Block List', + description: '', + dataType: { id: 'dt-blockList' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: -2, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + { + id: '22', + container: { id: 'content-group-key' }, + alias: 'blockGrid', + name: 'Block Grid', + description: '', + dataType: { id: 'dt-blockGrid' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: -1, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'content-group-key', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedDocumentTypes: [], + compositions: [], + cleanup: { + preventCleanup: false, + keepAllVersionsNewerThanDays: null, + keepLatestVersionPerDayForDays: null, + }, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts index ffbeef1e24be..06bea9ff34a7 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/document/document.data.ts @@ -929,4 +929,267 @@ export const data: Array = [ }, ], }, + { + urls: [ + { + culture: 'en-US', + url: '/', + }, + ], + template: null, + id: 'block-editors-document-id', + parent: null, + documentType: { + id: 'block-editors-document-type-id', + icon: 'icon-document', + }, + hasChildren: false, + noAccess: false, + isProtected: false, + isTrashed: false, + values: [ + { + editorAlias: 'Umbraco.BlockList', + alias: 'blockList', + culture: null, + segment: null, + value: { + layout: { + 'Umbraco.BlockList': [ + { + contentKey: '1234', + settingsKey: '5678', + }, + { + contentKey: '1234-headline', + settingsKey: '1234-headline-settings', + }, + ], + }, + contentData: [ + { + key: '1234', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello world', + }, + ], + }, + { + key: '1234-headline', + contentTypeKey: 'headline-umbraco-demo-block-id', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'headline', + culture: null, + segment: null, + value: 'Hello world', + }, + ], + }, + ], + settingsData: [ + { + key: '5678', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Im in settings', + }, + ], + }, + { + key: '1234-headline-settings', + contentTypeKey: 'headline-settings-demo-block-id', + values: [], + }, + ], + expose: [ + { + contentKey: '1234', + culture: null, + segment: null, + }, + ], + }, + }, + { + editorAlias: 'Umbraco.BlockGrid', + alias: 'blockGrid', + culture: null, + segment: null, + value: { + layout: { + 'Umbraco.BlockGrid': [ + { + contentKey: '1234', + settingsKey: '5678', + columnSpan: 12, + areas: [ + { + key: 'area1_key', + items: [ + { + contentKey: 'a1234', + settingsKey: 'a5678', + columnSpan: 3, + rowSpan: 2, + }, + { + contentKey: 'c1234', + columnSpan: 3, + }, + ], + }, + { + key: 'area2_key', + items: [ + { + contentKey: 'b1234', + settingsKey: 'b5678', + columnSpan: 6, + areas: [], + }, + ], + }, + ], + }, + ], + }, + contentData: [ + { + key: '1234', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Im in settings', + }, + ], + }, + { + key: 'a1234', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello world from area 1', + }, + ], + }, + { + key: 'b1234', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello world from area 2', + }, + ], + }, + { + key: 'c1234', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello CCC from area 1', + }, + ], + }, + ], + settingsData: [ + { + key: '5678', + contentTypeKey: 'all-property-editors-document-type-id', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello world settings', + }, + ], + }, + { + key: 'a5678', + contentTypeKey: 'all-property-editors-document-type-id', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello world from area 1 settings', + }, + ], + }, + { + key: 'b5678', + contentTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c', + values: [ + { + editorAlias: 'Umbraco.TextBox', + alias: 'elementProperty', + culture: null, + segment: null, + value: 'Hello world from area 2 settings', + }, + ], + }, + ], + expose: [ + { + contentKey: '1234', + culture: null, + segment: null, + }, + ], + }, + }, + ], + variants: [ + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:31:51.354764', + culture: 'en-US', + segment: null, + name: 'All Block Editors', + createDate: '2023-02-06T15:31:46.876902', + updateDate: '2023-02-06T15:31:51.354764', + }, + { + state: DocumentVariantStateModel.PUBLISHED, + publishDate: '2023-02-06T15:31:51.354764', + culture: 'da-dk', + segment: null, + name: 'Alle blok redigeringer', + createDate: '2023-02-06T15:31:46.876902', + updateDate: '2023-02-06T15:31:51.354764', + }, + ], + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts index 9378249bf40e..389ec141bae2 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/media-type/media-type.data.ts @@ -15,7 +15,7 @@ export const data: Array = [ parent: null, description: 'Media type 1 description', alias: 'mediaType1', - icon: 'icon-bug', + icon: 'icon-picture', properties: [ { id: '19', @@ -99,4 +99,204 @@ export const data: Array = [ isDeletable: false, aliasCanBeChanged: false, }, + { + name: 'Media Type 2', + id: 'media-type-2-id', + parent: null, + description: 'Media type 2 description', + alias: 'mediaType2', + icon: 'icon-audio-lines', + properties: [ + { + id: '19', + container: { id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75' }, + alias: 'umbracoFile', + name: 'File', + description: '', + dataType: { id: 'dt-uploadField' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedMediaTypes: [{ mediaType: { id: 'media-type-2-id' }, sortOrder: 0 }], + compositions: [], + isFolder: false, + hasChildren: false, + collection: { id: 'dt-collectionView' }, + isDeletable: false, + aliasCanBeChanged: false, + }, + { + name: 'Media Type 3', + id: 'media-type-3-id', + parent: null, + description: 'Media type 3 description', + alias: 'mediaType3', + icon: 'icon-origami', + properties: [ + { + id: '19', + container: { id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75' }, + alias: 'umbracoFile', + name: 'File', + description: '', + dataType: { id: 'dt-uploadField' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedMediaTypes: [{ mediaType: { id: 'media-type-3-id' }, sortOrder: 0 }], + compositions: [], + isFolder: false, + hasChildren: false, + collection: { id: 'dt-collectionView' }, + isDeletable: false, + aliasCanBeChanged: false, + }, + { + name: 'Media Type 4', + id: 'media-type-4-id', + parent: null, + description: 'Media type 4 description', + alias: 'mediaType4', + icon: 'icon-video', + properties: [ + { + id: '19', + container: { id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75' }, + alias: 'umbracoFile', + name: 'File', + description: '', + dataType: { id: 'dt-uploadField' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedMediaTypes: [{ mediaType: { id: 'media-type-4-id' }, sortOrder: 0 }], + compositions: [], + isFolder: false, + hasChildren: false, + collection: { id: 'dt-collectionView' }, + isDeletable: false, + aliasCanBeChanged: false, + }, + { + name: 'Media Type 5', + id: 'media-type-5-id', + parent: null, + description: 'Media type 5 description', + alias: 'mediaType5', + icon: 'icon-document', + properties: [ + { + id: '19', + container: { id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75' }, + alias: 'umbracoFile', + name: 'File', + description: '', + dataType: { id: 'dt-uploadField' }, + variesByCulture: false, + variesBySegment: false, + sortOrder: 0, + validation: { + mandatory: true, + mandatoryMessage: null, + regEx: null, + regExMessage: null, + }, + appearance: { + labelOnTop: false, + }, + }, + ], + containers: [ + { + id: 'c3cd2f12-b7c4-4206-8d8b-27c061589f75', + parent: null, + name: 'Content', + type: 'Group', + sortOrder: 0, + }, + ], + allowedAsRoot: true, + variesByCulture: false, + variesBySegment: false, + isElement: false, + allowedMediaTypes: [{ mediaType: { id: 'media-type-5-id' }, sortOrder: 0 }], + compositions: [], + isFolder: false, + hasChildren: false, + collection: { id: 'dt-collectionView' }, + isDeletable: false, + aliasCanBeChanged: false, + }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/tree.handlers.ts b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/tree.handlers.ts index 2d113dee3501..75683e8e545f 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/tree.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/handlers/media-type/tree.handlers.ts @@ -19,4 +19,11 @@ export const treeHandlers = [ const response = umbMediaTypeMockDb.tree.getChildrenOf({ parentId, skip, take }); return res(ctx.status(200), ctx.json(response)); }), + + rest.get(umbracoPath(`/tree${UMB_SLUG}/ancestors`), (req, res, ctx) => { + const id = req.url.searchParams.get('descendantId'); + if (!id) return; + const response = umbMediaTypeMockDb.tree.getAncestorsOf({ descendantId: id }); + return res(ctx.status(200), ctx.json(response)); + }), ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index f57aa3857fe8..72a3dd82a5cd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -157,7 +157,7 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { -
${this.#renderInside()}
+ ${this.#renderInside()}
`; } @@ -184,10 +184,12 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { .args=${[this._ownerContentTypeName, this._variantName]}>`; } else { - return html` + - `; + + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index ffa931e21a4e..b44d1f90c54d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -107,9 +107,6 @@ const SORTER_CONFIG: UmbSorterConfig(this, { ...SORTER_CONFIG, @@ -198,7 +195,6 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen }, null, ); - this.observe( this.#context.amountOfAllowedBlockTypes, (length) => { @@ -253,6 +249,16 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen 'observeIsReadOnly', ); + this.observe( + manager.variantId, + (variantId) => { + if (variantId) { + this.#sorter.identifier = 'umb-block-grid-' + variantId.toString(); + } + }, + 'observeVariantId', + ); + if (this.areaKey) { this.observe( this.#context.areaTypeCreateLabel, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index e291af4aeb2a..02ed6b730f3b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -404,7 +404,16 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper if (ext.component) { ext.component.classList.add('umb-block-grid__block--view'); } - return ext.component; + if (this._exposed) { + return ext.component; + } else { + return html`
+ ${ext.component} + +
`; + } }; #renderInlineEditBlock() { @@ -555,6 +564,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper display: block; --umb-block-grid-entry-actions-opacity: 0; } + :host([settings-invalid]), :host([content-invalid]), :host(:hover), @@ -562,6 +572,23 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper --umb-block-grid-entry-actions-opacity: 1; } + :host::after { + content: ''; + position: absolute; + z-index: 1; + pointer-events: none; + inset: 0; + border: 1px solid transparent; + border-radius: var(--uui-border-radius); + + transition: border-color 240ms ease-in; + } + + :host([settings-invalid])::after, + :host([content-invalid])::after { + border-color: var(--uui-color-danger); + } + uui-action-bar { position: absolute; top: var(--uui-size-2); @@ -591,17 +618,6 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper height: 100%; } - :host::after { - content: ''; - position: absolute; - z-index: 1; - pointer-events: none; - inset: 0; - border: 1px solid transparent; - border-radius: var(--uui-border-radius); - - transition: border-color 240ms ease-in; - } :host(:hover):not(:drop)::after { display: block; border-color: var(--uui-color-interactive-emphasis); @@ -630,15 +646,6 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper opacity: 0; } - :host([settings-invalid])::after, - :host([content-invalid])::after { - border-color: var(--uui-color-danger); - } - :host([settings-invalid])::before, - :host([content-invalid])::before { - background-color: var(--uui-color-danger); - } - uui-badge { z-index: 2; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/ref-grid-block/ref-grid-block.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/ref-grid-block/ref-grid-block.element.ts index 5020698b4eed..2d97c182fc68 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/ref-grid-block/ref-grid-block.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/ref-grid-block/ref-grid-block.element.ts @@ -31,6 +31,7 @@ export class UmbRefGridBlockElement extends UUIRefNodeElement { } #open-part { + display: flex; min-height: var( --uui-size-layout-2 ); /* TODO: We should not do this, but it is a quick fix for now to ensure that the top part of a block gets a minimum height. */ diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts index cd4b63e0a554..fc0e104b2e30 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -71,7 +71,7 @@ export class UmbBlockGridManagerContext< override setEditorConfiguration(configs: UmbPropertyEditorConfigCollection) { this.#initAppUrl.then(() => { // we await initAppUrl, So the appUrl begin here is available when retrieving the layoutStylesheet. - this._editorConfiguration.setValue(configs); + super.setEditorConfiguration(configs); }); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index d3b58036f7ee..43b85da13a83 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -13,6 +13,8 @@ import type { ManifestBlockEditorCustomView, UmbBlockEditorCustomViewProperties, } from '@umbraco-cms/backoffice/block-custom-view'; +import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { UUIBlinkAnimationValue } from '@umbraco-cms/backoffice/external/uui'; /** * @element umb-block-list-entry @@ -277,6 +279,19 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper return true; }; + #extensionSlotRenderMethod = (ext: UmbExtensionElementInitializer) => { + if (this._exposed) { + return ext.component; + } else { + return html`
+ ${ext.component} + +
`; + } + }; + #renderRefBlock() { return html`${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()} - - ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()} - - ${!this._showContentEdit && this._contentInvalid - ? html`!` - : nothing} +
+ ${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()} + + ${this.#renderEditContentAction()} ${this.#renderEditSettingsAction()} ${this.#renderDeleteAction()} + + ${!this._showContentEdit && this._contentInvalid + ? html`!` + : nothing} +
` : nothing; } @@ -385,6 +403,23 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper --umb-block-list-entry-actions-opacity: 1; } + :host::after { + content: ''; + position: absolute; + z-index: 1; + pointer-events: none; + inset: 0; + border: 1px solid transparent; + border-radius: var(--uui-border-radius); + + transition: border-color 240ms ease-in; + } + + :host([settings-invalid])::after, + :host([content-invalid])::after { + border-color: var(--uui-color-danger); + } + uui-action-bar { position: absolute; top: var(--uui-size-2); @@ -393,23 +428,47 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper transition: opacity 120ms; } - :host([drag-placeholder]) { - opacity: 0.2; - --umb-block-list-entry-actions-opacity: 0; + uui-badge { + z-index: 2; } - :host([settings-invalid])::after, - :host([content-invalid])::after { + :host::after { content: ''; position: absolute; - inset: 0; + z-index: 1; pointer-events: none; - border: 1px solid var(--uui-color-danger); + inset: 0; + border: 1px solid transparent; border-radius: var(--uui-border-radius); + + transition: border-color 240ms ease-in; + } + :host(:hover):not(:drop)::after { + display: block; + border-color: var(--uui-color-interactive-emphasis); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.7), + inset 0 0 0 1px rgba(255, 255, 255, 0.7); } - uui-badge { - z-index: 2; + :host([drag-placeholder])::after { + display: block; + border-width: 2px; + border-color: var(--uui-color-interactive-emphasis); + animation: ${UUIBlinkAnimationValue}; + } + :host([drag-placeholder])::before { + content: ''; + position: absolute; + pointer-events: none; + inset: 0; + border-radius: var(--uui-border-radius); + background-color: var(--uui-color-interactive-emphasis); + opacity: 0.12; + } + :host([drag-placeholder]) .umb-block-list__block { + transition: opacity 50ms 16ms; + opacity: 0; } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts index e5f8fd348676..2f325a36bc0a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts @@ -14,6 +14,7 @@ import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; import '../ref-rte-block/index.js'; import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation'; import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block'; +import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api'; /** * @class UmbBlockRteEntryElement @@ -242,12 +243,26 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert this.#context.expose(); }; + #extensionSlotRenderMethod = (ext: UmbExtensionElementInitializer) => { + if (this._exposed) { + return ext.component; + } else { + return html`
+ ${ext.component} + +
`; + } + }; + #renderBlock() { return html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/components/block-overlay-expose-button/block-overlay-expose-button.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/components/block-overlay-expose-button/block-overlay-expose-button.element.ts new file mode 100644 index 000000000000..0c255badf332 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/components/block-overlay-expose-button/block-overlay-expose-button.element.ts @@ -0,0 +1,48 @@ +import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +/** + * @description + * This element is used to render a expose button that goes on top of a block. + */ +@customElement('umb-block-overlay-expose-button') +export class UmbBlockOverlayExposeButtonElement extends UmbLitElement { + @property({ attribute: false }) + contentTypeName?: string; + + override render() { + return this.contentTypeName + ? html` + ` + : nothing; + } + + static override styles = [ + css` + :host { + position: absolute; + inset: 0; + } + + uui-button { + position: absolute; + inset: 0; + opacity: 0.8; + } + + :host:hover uui-button { + opacity: 1; + } + `, + ]; +} + +export default UmbBlockOverlayExposeButtonElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-block-overlay-expose-button': UmbBlockOverlayExposeButtonElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/components/index.ts new file mode 100644 index 000000000000..bf8ce8160386 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/components/index.ts @@ -0,0 +1 @@ +export * from './block-overlay-expose-button/block-overlay-expose-button.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts index 9517b5c320c2..715fe3a6b8d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts @@ -91,7 +91,7 @@ export abstract class UmbBlockEntryContext< _layout = new UmbObjectState(undefined); public readonly layout = this._layout.asObservable(); public readonly contentKey = this._layout.asObservablePart((x) => x?.contentKey); - public readonly settingsKey = this._layout.asObservablePart((x) => x?.settingsKey); + public readonly settingsKey = this._layout.asObservablePart((x) => (x ? (x.settingsKey ?? null) : undefined)); public readonly unique = this._layout.asObservablePart((x) => x?.contentKey); #label = new UmbStringState(''); @@ -218,7 +218,7 @@ export abstract class UmbBlockEntryContext< #settings = new UmbObjectState(undefined); //public readonly settings = this.#settings.asObservable(); - protected readonly _settingsValueArray = this.#content.asObservablePart((x) => x?.values); + protected readonly _settingsValueArray = this.#settings.asObservablePart((x) => x?.values); private readonly settingsDataContentTypeKey = this.#settings.asObservablePart((x) => x ? (x.contentTypeKey ?? undefined) : null, ); @@ -296,7 +296,6 @@ export abstract class UmbBlockEntryContext< this.settingsDataContentTypeKey, (settingsElementTypeKey) => { if (!settingsElementTypeKey) return; - this.#getSettingsStructure(settingsElementTypeKey); }, null, @@ -335,17 +334,17 @@ export abstract class UmbBlockEntryContext< ); this.observe( - observeMultiple([this.layout, this.blockType]), - ([layout, blockType]) => { - if (!this.#contentKey || !layout || !blockType) return; - if (layout.settingsKey == null && blockType.settingsElementTypeKey) { + observeMultiple([this.settingsKey, this.blockType]), + ([settingsKey, blockType]) => { + if (!this.#contentKey || settingsKey === undefined || !blockType) return; + if (settingsKey == null && blockType.settingsElementTypeKey) { // We have a settings ElementType in config but not in data, so lets create the scaffold for that: [NL] const settingsData = this._manager!.createBlockSettingsData(blockType.contentElementTypeKey); // Yes its on purpose we use the contentElementTypeKey here, as this is our identifier for a BlockType. [NL] this._manager?.setOneSettings(settingsData); this._layout.update({ settingsKey: settingsData.key } as Partial); - } else if (layout.settingsKey && blockType.settingsElementTypeKey === undefined) { + } else if (settingsKey && blockType.settingsElementTypeKey === undefined) { // We no longer have settings ElementType in config. So we remove the settingsData and settings key from the layout. [NL] - this._manager?.removeOneSettings(layout.settingsKey); + this._manager?.removeOneSettings(settingsKey); this._layout.update({ settingsKey: undefined } as Partial); } }, @@ -464,18 +463,22 @@ export abstract class UmbBlockEntryContext< ); } #observeSettingsData() { - if (!this._manager) return; // observe settings: - const settingsKey = this._layout.value?.settingsKey; - if (settingsKey) { - this.observe( - this._manager.settingsOf(settingsKey), - (settings) => { - this.#settings.setValue(settings); - }, - 'observeSettings', - ); - } + this.observe( + this._manager ? this.settingsKey : undefined, + (settingsKey) => { + if (settingsKey) { + this.observe( + this._manager?.settingsOf(settingsKey), + (settings) => { + this.#settings.setValue(settings); + }, + 'observeSettings', + ); + } + }, + 'observeSettingsKey', + ); } abstract _gotContentType(contentType: UmbContentTypeModel | undefined): void; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts index 216c98bbe6c1..fd6ca0502c2a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts @@ -1,3 +1,4 @@ +export * from './components/index.js'; export * from './context/index.js'; export * from './modals/index.js'; export * from './property-value-resolver/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts index dc7f737b0e76..f807bc2fb171 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts @@ -249,7 +249,6 @@ export class UmbBlockWorkspaceContext = []; - if (views?.length) { + if (views && views.length > 0) { // find the default view from the config. If it doesn't exist, use the first view const defaultView = views.find((view) => view.alias === this.#defaultViewAlias); const fallbackView = defaultView?.meta.pathName || views[0].meta.pathName; @@ -92,17 +92,19 @@ export class UmbCollectionViewManager extends UmbControllerBase { }; }); + if (routes.length > 0) { + routes.push({ + path: '', + redirectTo: fallbackView, + }); + } + routes.push({ - path: '', - redirectTo: fallbackView, + path: `**`, + component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, }); } - routes.push({ - path: `**`, - component: async () => (await import('@umbraco-cms/backoffice/router')).UmbRouteNotFoundElement, - }); - this.#routes.setValue(routes); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-selection-actions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-selection-actions.element.ts index 8beb70b56c6d..389f1d22acf1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-selection-actions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-selection-actions.element.ts @@ -1,20 +1,29 @@ import { UMB_COLLECTION_CONTEXT } from '../default/index.js'; -import type { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, nothing, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity'; import type { ManifestEntityBulkAction, MetaEntityBulkAction } from '@umbraco-cms/backoffice/extension-registry'; +import type { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; /** - * - * @param manifest + * Generates API arguments for the given manifest. + * @param {string | null | undefined} entityType - The type of the entity. + * @param {ManifestEntityBulkAction} manifest - The manifest object from the extension. + * @returns {Array} An array with the meta object from the manifest. */ -function apiArgsMethod(manifest: ManifestEntityBulkAction) { - return [{ meta: manifest.meta }] as unknown[]; +function apiArgsMethod( + entityType: string | null | undefined, + manifest: ManifestEntityBulkAction, +): Array { + return [{ entityType, meta: manifest.meta }] as Array; } @customElement('umb-collection-selection-actions') export class UmbCollectionSelectionActionsElement extends UmbLitElement { + @state() + private _entityType?: string | null; + @state() private _totalItems = 0; @@ -34,6 +43,10 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { this._collectionContext = instance; this._observeCollectionContext(); }); + + this.consumeContext(UMB_ENTITY_CONTEXT, (entityContext) => { + this._entityType = entityContext.getEntityType(); + }); } private _handleKeyDown(event: KeyboardEvent) { @@ -96,7 +109,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement { type="entityBulkAction" default-element="umb-entity-bulk-action" .apiProps=${this._apiProps} - .apiArgs=${apiArgsMethod} + .apiArgs=${(manifest: ManifestEntityBulkAction) => apiArgsMethod(this._entityType, manifest)} @action-executed=${this.#onActionExecuted}>
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts index d26d5aaccdd8..5dc728345071 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts @@ -1,6 +1,7 @@ import { UMB_COLLECTION_CONTEXT } from '../default/index.js'; import type { ManifestCollectionView } from '../extensions/index.js'; import type { UmbCollectionLayoutConfiguration } from '../types.js'; +import { UMB_ROUTE_CONTEXT } from '../../router/route.context.js'; import { css, customElement, html, nothing, query, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -34,6 +35,12 @@ export class UmbCollectionViewBundleElement extends UmbLitElement { constructor() { super(); + this.consumeContext(UMB_ROUTE_CONTEXT, (context) => { + this.observe(context.activePath, (activePath) => { + this._collectionRootPathName = activePath; + }); + }); + this.consumeContext(UMB_COLLECTION_CONTEXT, (context) => { this.#collectionContext = context; this.#observeCollection(); @@ -47,14 +54,6 @@ export class UmbCollectionViewBundleElement extends UmbLitElement { #observeCollection() { if (!this.#collectionContext) return; - this.observe( - this.#collectionContext.view.rootPathName, - (rootPathName) => { - this._collectionRootPathName = rootPathName; - }, - 'umbCollectionRootPathNameObserver', - ); - this.observe( this.#collectionContext.view.currentView, (currentView) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts index 885ad3f982bf..7ae41de4bc62 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts @@ -4,6 +4,7 @@ import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@um import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ export class UmbCollectionBulkActionPermissionCondition extends UmbConditionBase implements UmbExtensionCondition @@ -21,4 +22,5 @@ export class UmbCollectionBulkActionPermissionCondition } } +/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ export default UmbCollectionBulkActionPermissionCondition; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts index 36fa086ad7b3..8a94507bbfcc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts @@ -1,3 +1,4 @@ export const UMB_COLLECTION_ALIAS_CONDITION = 'Umb.Condition.CollectionAlias'; +/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ export const UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION = 'Umb.Condition.CollectionBulkActionPermission'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts index 3e4eadfc6235..28e92324164c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts @@ -8,6 +8,7 @@ export const manifests: Array = [ alias: UMB_COLLECTION_ALIAS_CONDITION, api: () => import('./collection-alias.condition.js'), }, + /** @deprecated No longer used internally. This class will be removed in Umbraco 17. [LK] */ { type: 'condition', name: 'Collection Bulk Action Permission Condition', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts index 6a19e0a3c9fa..6727375a9e8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts @@ -2,6 +2,7 @@ import type { UmbCollectionBulkActionPermissions } from '../types.js'; import type { UMB_COLLECTION_ALIAS_CONDITION, UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION } from './constants.js'; import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; +/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ export type CollectionBulkActionPermissionConditionConfig = UmbConditionConfigBase< typeof UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION > & { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts index be002996d744..d61a4c0d9fad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.context.ts @@ -11,7 +11,7 @@ import type { UmbCollectionRepository } from '../repository/collection-repositor import type { ManifestCollection } from '../extensions/index.js'; import { UMB_COLLECTION_CONTEXT } from './collection-default.context-token.js'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbArrayState, UmbNumberState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbArrayState, UmbBasicState, UmbNumberState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; @@ -26,6 +26,8 @@ import { import type { UmbActionEventContext } from '@umbraco-cms/backoffice/action'; import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity'; +import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; +import { UmbModalRouteRegistrationController, type UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; const LOCAL_STORAGE_KEY = 'umb-collection-view'; @@ -53,6 +55,9 @@ export class UmbDefaultCollectionContext< protected _filter = new UmbObjectState({}); public readonly filter = this._filter.asObservable(); + #workspacePathBuilder = new UmbBasicState(undefined); + public readonly workspacePathBuilder = this.#workspacePathBuilder.asObservable(); + #userDefinedProperties = new UmbArrayState([], (x) => x.alias); public readonly userDefinedProperties = this.#userDefinedProperties.asObservable(); @@ -89,6 +94,25 @@ export class UmbDefaultCollectionContext< this.#listenToEntityEvents(); } + setupView(viewElement: UmbControllerHost) { + new UmbModalRouteRegistrationController(viewElement, UMB_WORKSPACE_MODAL) + .addAdditionalPath('entity/:entityType') + .onSetup((params) => { + return { data: { entityType: params.entityType, preset: {} } }; + }) + .onReject(() => { + // TODO: Maybe this can be removed? + this.requestCollection(); + }) + .onSubmit(() => { + // TODO: Maybe this can be removed? + this.requestCollection(); + }) + .observeRouteBuilder((routeBuilder) => { + this.#workspacePathBuilder.setValue(routeBuilder); + }); + } + async #listenToEntityEvents() { this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => { this.#actionEventContext = context; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts index 4350fdf0a7b4..944813b89281 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/default/collection-default.element.ts @@ -70,12 +70,14 @@ export class UmbCollectionDefaultElement extends UmbLitElement { } override render() { - return html` - - - ${this.renderToolbar()} ${this._hasItems ? this.#renderContent() : this.#renderEmptyState()} - - `; + return this._routes + ? html` + + + ${this.renderToolbar()} ${this._hasItems ? this.#renderContent() : this.#renderEmptyState()} + + ` + : nothing; } protected renderToolbar() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts index 352a8176c740..a083dd8bebb3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts @@ -5,6 +5,7 @@ import type { UmbPaginationManager } from '@umbraco-cms/backoffice/utils'; export type * from './extensions/index.js'; export type * from './conditions/types.js'; +/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ export interface UmbCollectionBulkActionPermissions { allowBulkCopy: boolean; allowBulkDelete: boolean; @@ -16,6 +17,7 @@ export interface UmbCollectionBulkActionPermissions { export interface UmbCollectionConfiguration { unique?: string; dataTypeId?: string; + /** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ allowedEntityBulkActions?: UmbCollectionBulkActionPermissions; layouts?: Array; orderBy?: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts index bf16c8752490..e3294c5283ff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/repository/structure/content-type-structure-server-data-source-base.ts @@ -1,10 +1,11 @@ import type { UmbPagedModel } from '../../../repository/types.js'; import type { UmbContentTypeStructureDataSource } from './content-type-structure-data-source.interface.js'; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; -// Temp type -type AllowedContentTypeModel = { +// Keep this type internal +type AllowedContentTypeBaseModel = { id: string; name: string; description?: string | null; @@ -12,16 +13,16 @@ type AllowedContentTypeModel = { }; export interface UmbContentTypeStructureServerDataSourceBaseArgs< - ServerItemType extends AllowedContentTypeModel, - ClientItemType extends { unique: string }, + ServerItemType extends AllowedContentTypeBaseModel, + ClientItemType extends UmbEntityModel, > { getAllowedChildrenOf: (unique: string | null) => Promise>; mapper: (item: ServerItemType) => ClientItemType; } export abstract class UmbContentTypeStructureServerDataSourceBase< - ServerItemType extends AllowedContentTypeModel, - ClientItemType extends { unique: string }, + ServerItemType extends AllowedContentTypeBaseModel, + ClientItemType extends UmbEntityModel, > implements UmbContentTypeStructureDataSource { #host; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/collection/content-collection-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/collection/content-collection-workspace-view.element.ts index f9a8b7dc1905..7a04140ceccf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/collection/content-collection-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/collection/content-collection-workspace-view.element.ts @@ -1,4 +1,4 @@ -import type { UmbCollectionBulkActionPermissions, UmbCollectionConfiguration } from '../../collection/types.js'; +import type { UmbCollectionConfiguration } from '../../collection/types.js'; import { UMB_CONTENT_COLLECTION_WORKSPACE_CONTEXT } from './content-collection-workspace.context-token.js'; import { customElement, html, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @@ -64,7 +64,6 @@ export class UmbContentCollectionWorkspaceViewElement extends UmbLitElement impl const pageSize = Number(config.getValueByAlias('pageSize')); return { unique: this._documentUnique, - allowedEntityBulkActions: config?.getValueByAlias('bulkActionPermissions'), layouts: config?.getValueByAlias('layouts'), orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate', orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts index ae4e35af835e..dbf4bb00630d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts @@ -32,14 +32,12 @@ export class UmbMergeContentVariantDataController extends UmbControllerBase { // Combine data and persisted data depending on the selectedVariants. Always use the invariant values from the data. // loops over each entry in values, determine wether the value should be from the data or the persisted data, depending on wether its a selectedVariant or an invariant value. // loops over each entry in variants, determine wether the variant should be from the data or the persisted data, depending on the selectedVariants. - const result = { - ...currentData, - values: await this.#processValues( - persistedData?.values, - currentData.values, - variantsToStore, - ), - }; + const result = { ...currentData }; + result.values = await this.#processValues( + persistedData?.values, + currentData.values, + variantsToStore, + ); if (currentData.variants) { // Notice for variants we do not want to include all the variants that we are processing. but just the once selected for the process. (Not include invariant if we are in a variant document) [NL] diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts index bd3b2f951778..814ffafd884c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-detail-workspace-base.ts @@ -470,24 +470,11 @@ export abstract class UmbContentDetailWorkspaceContextBase< } } - /** - * Request a submit of the workspace, in the case of Content Workspaces the validation does not need to be valid for this to be submitted. - * @returns {Promise} a promise which resolves once it has been completed. - */ - public override requestSubmit() { - return this.#handleSubmit(); - } - public override submit() { - return this.#handleSubmit(); - } - - // Because we do not make validation prevent submission this also submits the workspace. [NL] - public override invalidSubmit() { - return this.#handleSubmit(); + return this._handleSubmit(); } - async #handleSubmit() { + protected async _handleSubmit() { const data = this.getData(); if (!data) { throw new Error('Data is missing'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json index d35848792777..2cd9c3caeecd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json @@ -72,6 +72,10 @@ "name": "icon-attachment", "file": "paperclip.svg" }, + { + "name": "icon-audio-lines", + "file": "audio-lines.svg" + }, { "name": "icon-autofill", "file": "text-cursor-input.svg" @@ -1374,6 +1378,10 @@ "name": "icon-ordered-list", "file": "list-ordered.svg" }, + { + "name": "icon-origami", + "file": "origami.svg" + }, { "name": "icon-out", "file": "external-link.svg" @@ -2712,4 +2720,4 @@ "file": "icon-umbraco.svg" } ] -} +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts index 8855310df5ae..f02b1643b8a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts @@ -71,6 +71,10 @@ name: "icon-attachment", path: () => import("./icons/icon-attachment.js"), },{ +name: "icon-audio-lines", + +path: () => import("./icons/icon-audio-lines.js"), +},{ name: "icon-autofill", path: () => import("./icons/icon-autofill.js"), @@ -1299,6 +1303,10 @@ name: "icon-ordered-list", path: () => import("./icons/icon-ordered-list.js"), },{ +name: "icon-origami", + +path: () => import("./icons/icon-origami.js"), +},{ name: "icon-out", path: () => import("./icons/icon-out.js"), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-audio-lines.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-audio-lines.ts new file mode 100644 index 000000000000..d2aa21c15467 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-audio-lines.ts @@ -0,0 +1,19 @@ +export default ` + + + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-origami.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-origami.ts new file mode 100644 index 000000000000..bf9f34a255a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-origami.ts @@ -0,0 +1,16 @@ +export default ` + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts index 4a5e877dfef9..450d762928b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/component/modal.element.ts @@ -35,7 +35,16 @@ export class UmbModalElement extends UmbLitElement { #modalExtensionObserver?: UmbObserverController; #modalRouterElement?: HTMLDivElement | UmbRouterSlotElement; - #onClose = () => { + #onClose = (e: Event) => { + if (this.#modalContext?.isResolved() === false) { + // If not resolved, and has a router, then we do not want to close, but instead let the router try to change the path for that to eventually trigger another round of close. + if (this.#modalContext?.router) { + e.stopImmediatePropagation(); + e.preventDefault(); + this.#modalContext._internal_removeCurrentModal(); + return; + } + } this.element?.removeEventListener(UUIModalCloseEvent, this.#onClose); this.#modalContext?.reject({ type: 'close' }); }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts index 2cd0dcbc21e8..9cb52e435943 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal-manager.context.ts @@ -12,6 +12,8 @@ export class UmbModalManagerContext extends UmbContextBase modal.key === key); if (modal) { - modal.reject(); + modal.forceResolve(); } } public remove(key: string) { this.#modals.setValue(this.#modals.getValue().filter((modal) => modal.key !== key)); } + + /** + * Closes all modals that is not routable + * @private + * @memberof UmbModalManagerContext + */ + #closeNoneRoutableModals() { + this.#modals + .getValue() + .filter((modal) => modal.router === null) + .forEach((modal) => { + modal.forceResolve(); + }); + } + + #onNavigationSuccess = () => { + this.#closeNoneRoutableModals(); + }; + + override destroy() { + super.destroy(); + this.#modals.destroy(); + window.removeEventListener('navigationsuccess', this.#onNavigationSuccess); + } } export const UMB_MODAL_MANAGER_CONTEXT = new UmbContextToken( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts index 67559025ed85..991bbddf86fa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/context/modal.context.ts @@ -1,4 +1,5 @@ import { UmbModalToken } from '../token/modal-token.js'; +import { UMB_ROUTE_CONTEXT } from '../../router/route.context.js'; import type { UmbModalConfig, UmbModalType } from '../types.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; @@ -31,9 +32,19 @@ export class UmbModalContext< ModalValue = any, > extends UmbControllerBase { // + // TODO: Come up with a better name: + #submitIsGood?: boolean; + #submitRejectReason?: UmbModalRejectReason; + #submitIsResolved?: boolean; + #submitPromise: Promise; #submitResolver?: (value: ModalValue) => void; #submitRejecter?: (reason?: UmbModalRejectReason) => void; + #markAsResolved() { + this.#submitResolver = undefined; + this.#submitRejecter = undefined; + this.#submitIsResolved = true; + } public readonly key: string; public readonly data: ModalData; @@ -73,8 +84,6 @@ export class UmbModalContext< this.element = args.modal?.element || this.element; this.backdropBackground = args.modal?.backdropBackground || this.backdropBackground; - console.log('size', size); - this.#size.setValue(size); const defaultData = this.alias instanceof UmbModalToken ? this.alias.getDefaultData() : undefined; @@ -99,6 +108,35 @@ export class UmbModalContext< }); } + #activeModalPath?: string; + + _internal_setCurrentModalPath(path: string) { + this.#activeModalPath = path; + } + + async _internal_removeCurrentModal() { + const routeContext = await this.getContext(UMB_ROUTE_CONTEXT); + routeContext._internal_removeModalPath(this.#activeModalPath); + } + + forceResolve() { + // THIS should close the element no matter what. + if (this.#submitIsGood) { + const resolver = this.#submitResolver; + this.#markAsResolved(); + resolver?.(this.getValue()); + } else { + // We might store the reason from reject and use it here?, but I'm not sure what the real need is for this reason. [NL] + const resolver = this.#submitRejecter; + this.#markAsResolved(); + resolver?.(this.#submitRejectReason ?? { type: 'close' }); + } + } + + isResolved() { + return this.#submitIsResolved === true; + } + // note, this methods is private argument is not defined correctly here, but requires to be fix by appending the OptionalSubmitArgumentIfUndefined type when newing up this class. /** * Submits this modal, returning with a value to the initiator of the modal. @@ -106,7 +144,15 @@ export class UmbModalContext< * @memberof UmbModalContext */ public submit() { + if (this.#submitIsResolved) return; + if (this.router) { + // Do not resolve jet, lets try to change the URL. + this.#submitIsGood = true; + this._internal_removeCurrentModal(); + return; + } this.#submitResolver?.(this.getValue()); + this.#markAsResolved(); // TODO: Could we clean up this class here? (Example destroy the value state, and other things?) } @@ -117,7 +163,16 @@ export class UmbModalContext< * @memberof UmbModalContext */ public reject(reason?: UmbModalRejectReason) { + if (this.#submitIsResolved) return; + if (this.router) { + // Do not reject jet, lets try to change the URL. + this.#submitIsGood = false; + this.#submitRejectReason = reason; + this._internal_removeCurrentModal(); + return; + } this.#submitRejecter?.(reason); + this.#markAsResolved(); // TODO: Could we clean up this class here? (Example destroy the value state, and other things?) } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts index 5e176c8dcf1f..6ba9b8846e8e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/router/route.context.ts @@ -1,11 +1,12 @@ import type { UmbRoute } from './route.interface.js'; import { umbGenerateRoutePathBuilder } from './generate-route-path-builder.function.js'; import type { UmbModalRouteRegistration } from './modal-registration/modal-route-registration.interface.js'; -import type { IRoutingInfo, IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; +import type { IRouterSlot } from '@umbraco-cms/backoffice/external/router-slot'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UmbStringState, mergeObservables } from '@umbraco-cms/backoffice/observable-api'; const EmptyDiv = document.createElement('div'); @@ -16,10 +17,17 @@ export class UmbRouteContext extends UmbContextBase { #modalRegistrations: UmbModalRouteRegistration[] = []; #modalContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; #modalRoutes: UmbRoutePlusModalKey[] = []; - #routerBasePath?: string; - #routerActiveLocalPath?: string; #activeModalPath?: string; + #basePath = new UmbStringState(undefined); + public readonly basePath = this.#basePath.asObservable(); + + #activeLocalPath = new UmbStringState(undefined); + public readonly activeLocalPath = this.#activeLocalPath.asObservable(); + public readonly activePath = mergeObservables([this.basePath, this.activeLocalPath], ([basePath, localPath]) => { + return basePath + '/' + localPath; + }); + constructor(host: UmbControllerHost, mainRouter: IRouterSlot, modalRouter: IRouterSlot) { super(host, UMB_ROUTE_CONTEXT); this.#modalRouter = modalRouter; @@ -55,24 +63,17 @@ export class UmbRouteContext extends UmbContextBase { info.match.params, ); if (modalContext) { - modalContext.onSubmit().then( - () => { - this.#removeModalPath(info); - }, - () => { - this.#removeModalPath(info); - }, - ); + modalContext._internal_setCurrentModalPath(info.match.fragments.consumed); } }, }; } - #removeModalPath(info: IRoutingInfo) { + _internal_removeModalPath(folderToRemove?: string) { // Reset the URL to the routerBasePath + routerActiveLocalPath [NL] - const folderToRemove = info.match.fragments.consumed; if (folderToRemove && window.location.href.includes(folderToRemove)) { - window.history.pushState({}, '', this.#routerBasePath + '/' + this.#routerActiveLocalPath); + const url = this.#basePath.getValue() + '/' + this.#activeLocalPath.getValue(); + window.history.pushState({}, '', url); } } @@ -106,14 +107,14 @@ export class UmbRouteContext extends UmbContextBase { } public _internal_routerGotBasePath(routerBasePath: string) { - if (this.#routerBasePath === routerBasePath) return; - this.#routerBasePath = routerBasePath; + if (this.#basePath.getValue() === routerBasePath) return; + this.#basePath.setValue(routerBasePath); this.#createNewUrlBuilders(); } public _internal_routerGotActiveLocalPath(routerActiveLocalPath: string | undefined) { - if (this.#routerActiveLocalPath === routerActiveLocalPath) return; - this.#routerActiveLocalPath = routerActiveLocalPath; + if (this.#activeLocalPath.getValue() === routerActiveLocalPath) return; + this.#activeLocalPath.setValue(routerActiveLocalPath); this.#createNewUrlBuilders(); } @@ -137,13 +138,16 @@ export class UmbRouteContext extends UmbContextBase { } #createNewUrlBuilder = (modalRegistration: UmbModalRouteRegistration) => { - if (!this.#routerBasePath) return; + const routerBasePath = this.#basePath.getValue(); + if (!routerBasePath) return; + + const activeLocalPath = this.#activeLocalPath.getValue(); - const routeBasePath = this.#routerBasePath.endsWith('/') ? this.#routerBasePath : this.#routerBasePath + '/'; - const routeActiveLocalPath = this.#routerActiveLocalPath - ? this.#routerActiveLocalPath.endsWith('/') - ? this.#routerActiveLocalPath - : this.#routerActiveLocalPath + '/' + const routeBasePath = routerBasePath.endsWith('/') ? routerBasePath : routerBasePath + '/'; + const routeActiveLocalPath = activeLocalPath + ? activeLocalPath.endsWith('/') + ? activeLocalPath + : activeLocalPath + '/' : ''; const localPath = routeBasePath + routeActiveLocalPath + modalRegistration.generateModalPath(); const urlBuilder = umbGenerateRoutePathBuilder(localPath); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 0f13d130ae0c..c8e22a1f75a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -203,7 +203,7 @@ export type UmbSorterConfig = Partial, 'ignorerSelector' | 'containerSelector' | 'identifier'>>; /** - + * @class UmbSorterController * @implements {UmbControllerInterface} * @description This controller can make user able to sort items. @@ -265,6 +265,12 @@ export class UmbSorterController) { super(host); @@ -460,7 +466,7 @@ export class UmbSorterController { - // If not good drop, revert model? - if (!UmbSorterController.activeElement || !UmbSorterController.activeItem) { return; } + // If browser thinks this was a cancelled move, we should revert the move. (based on dropEffect === 'none') [NL] + // But notice, this also count when releasing the mouse outside the sorters element, this i'm not sure if I agree on, would be ideal only to revert if ESC was pressed. [NL] if (UmbSorterController.originalSorter && event?.dataTransfer != null && event.dataTransfer.dropEffect === 'none') { // Revert move, to start position. UmbSorterController.originalSorter.moveItemInModel( diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts index 027552d30289..808fd08c6bb3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/document-type-structure.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_DOCUMENT_TYPE_ENTITY_TYPE } from '../../entity.js'; import type { UmbAllowedDocumentTypeModel } from './types.js'; import type { AllowedDocumentTypeModel } from '@umbraco-cms/backoffice/external/backend-api'; import { DocumentTypeService } from '@umbraco-cms/backoffice/external/backend-api'; @@ -32,6 +33,7 @@ const getAllowedChildrenOf = (unique: string | null) => { const mapper = (item: AllowedDocumentTypeModel): UmbAllowedDocumentTypeModel => { return { unique: item.id, + entityType: UMB_DOCUMENT_TYPE_ENTITY_TYPE, name: item.name, description: item.description || null, icon: item.icon || null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/types.ts index f0299ca6d93d..826457220289 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/repository/structure/types.ts @@ -1,5 +1,6 @@ -export interface UmbAllowedDocumentTypeModel { - unique: string; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbAllowedDocumentTypeModel extends UmbEntityModel { name: string; description: string | null; icon: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts index 4b0fafd6f8cd..7c4acaac8d02 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/action/create-document-collection-action.element.ts @@ -8,11 +8,10 @@ import { UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_DOCUMENT_WORKSPACE_CONTEXT, } from '@umbraco-cms/backoffice/document'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/collection'; import type { UmbAllowedDocumentTypeModel } from '@umbraco-cms/backoffice/document-type'; import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; @customElement('umb-create-document-collection-action') export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { @@ -20,10 +19,7 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { private _allowedDocumentTypes: Array = []; @state() - private _createDocumentPath = ''; - - @state() - private _currentView?: string; + private _workspacePathBuilder?: UmbModalRouteBuilder; @state() private _documentUnique?: UmbEntityUnique; @@ -34,9 +30,6 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { @state() private _popoverOpen = false; - @state() - private _rootPathName?: string; - @property({ attribute: false }) manifest?: ManifestCollectionAction; @@ -45,15 +38,6 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { constructor() { super(); - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath('document') - .onSetup(() => { - return { data: { entityType: 'document', preset: {} } }; - }) - .observeRouteBuilder((routeBuilder) => { - this._createDocumentPath = routeBuilder({}); - }); - this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (workspaceContext) => { this.observe(workspaceContext.unique, (unique) => { this._documentUnique = unique; @@ -64,11 +48,8 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { }); this.consumeContext(UMB_DOCUMENT_COLLECTION_CONTEXT, (collectionContext) => { - this.observe(collectionContext.view.currentView, (currentView) => { - this._currentView = currentView?.meta.pathName; - }); - this.observe(collectionContext.view.rootPathName, (rootPathName) => { - this._rootPathName = rootPathName; + this.observe(collectionContext.workspacePathBuilder, (builder) => { + this._workspacePathBuilder = builder; }); }); } @@ -95,14 +76,14 @@ export class UmbCreateDocumentCollectionActionElement extends UmbLitElement { } #getCreateUrl(item: UmbAllowedDocumentTypeModel) { - return ( - this._createDocumentPath.replace(`${this._rootPathName}`, `${this._rootPathName}/${this._currentView}`) + - UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ - parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE, - parentUnique: this._documentUnique ?? 'null', - documentTypeUnique: item.unique, - }) - ); + return item.unique && this._workspacePathBuilder + ? this._workspacePathBuilder({ entityType: UMB_DOCUMENT_ENTITY_TYPE }) + + UMB_CREATE_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ + parentEntityType: this._documentUnique ? UMB_DOCUMENT_ENTITY_TYPE : UMB_DOCUMENT_ROOT_ENTITY_TYPE, + parentUnique: this._documentUnique ?? 'null', + documentTypeUnique: item.unique, + }) + : ''; } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts index 685608462ec3..ab57b821e275 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/grid/document-grid-collection-view.element.ts @@ -5,9 +5,8 @@ import { getPropertyValueByAlias } from '../index.js'; import { css, customElement, html, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { fromCamelCase } from '@umbraco-cms/backoffice/utils'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import type { UmbDefaultCollectionContext, UmbCollectionColumnConfiguration } from '@umbraco-cms/backoffice/collection'; import type { UUIInterfaceColor } from '@umbraco-cms/backoffice/external/uui'; @@ -16,7 +15,7 @@ import '@umbraco-cms/backoffice/ufm'; @customElement('umb-document-grid-collection-view') export class UmbDocumentGridCollectionViewElement extends UmbLitElement { @state() - private _editDocumentPath = ''; + private _workspacePathBuilder?: UmbModalRouteBuilder; @state() private _items: Array = []; @@ -34,23 +33,16 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { this.consumeContext(UMB_DOCUMENT_COLLECTION_CONTEXT, (collectionContext) => { this.#collectionContext = collectionContext; + collectionContext.setupView(this); + this.observe( + collectionContext.workspacePathBuilder, + (builder) => { + this._workspacePathBuilder = builder; + }, + 'observePath', + ); this.#observeCollectionContext(); }); - - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath('document') - .onSetup(() => { - return { data: { entityType: 'document', preset: {} } }; - }) - .onReject(() => { - this.#collectionContext?.requestCollection(); - }) - .onSubmit(() => { - this.#collectionContext?.requestCollection(); - }) - .observeRouteBuilder((routeBuilder) => { - this._editDocumentPath = routeBuilder({}); - }); } #observeCollectionContext() { @@ -73,14 +65,6 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { ); } - #onOpen(event: Event, unique: string) { - event.preventDefault(); - event.stopPropagation(); - - const url = this._editDocumentPath + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ unique }); - window.history.pushState(null, '', url); - } - #onSelect(item: UmbDocumentCollectionItemModel) { this.#collectionContext?.selection.select(item.unique); } @@ -93,6 +77,15 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { return this.#collectionContext?.selection.isSelected(item.unique); } + #getEditUrl(item: UmbDocumentCollectionItemModel) { + return item.unique && this._workspacePathBuilder + ? this._workspacePathBuilder({ entityType: item.entityType }) + + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ + unique: item.unique, + }) + : ''; + } + override render() { return html`
@@ -112,7 +105,7 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { selectable ?select-only=${this._selection.length > 0} ?selected=${this.#isSelected(item)} - @open=${(event: Event) => this.#onOpen(event, item.unique)} + href=${this.#getEditUrl(item)} @selected=${() => this.#onSelect(item)} @deselected=${() => this.#onDeselect(item)}> @@ -158,7 +151,7 @@ export class UmbDocumentGridCollectionViewElement extends UmbLitElement { const value = getPropertyValueByAlias(item, column.alias); return html`
  • - ${column.header}: + ${this.localize.string(column.header)}: ${when( column.nameTemplate, () => html``, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts index c69efbe2288d..6629a7ae70c8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/document-table-collection-view.element.ts @@ -7,9 +7,7 @@ import type { UmbCollectionColumnConfiguration } from '@umbraco-cms/backoffice/c import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; -import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { UmbTableColumn, UmbTableConfig, @@ -25,6 +23,9 @@ import './column-layouts/document-table-column-state.element.js'; @customElement('umb-document-table-collection-view') export class UmbDocumentTableCollectionViewElement extends UmbLitElement { + @state() + private _workspacePathBuilder?: UmbModalRouteBuilder; + @state() private _userDefinedProperties?: Array; @@ -62,36 +63,21 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { #collectionContext?: UmbDocumentCollectionContext; - #routeBuilder?: UmbModalRouteBuilder; - constructor() { super(); + this.consumeContext(UMB_DOCUMENT_COLLECTION_CONTEXT, (collectionContext) => { this.#collectionContext = collectionContext; + collectionContext.setupView(this); + this.observe( + collectionContext.workspacePathBuilder, + (builder) => { + this._workspacePathBuilder = builder; + }, + 'observePath', + ); + this.#observeCollectionContext(); }); - - this.#registerModalRoute(); - } - - #registerModalRoute() { - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath(':entityType') - .onSetup((params) => { - return { data: { entityType: params.entityType, preset: {} } }; - }) - .onReject(() => { - this.#collectionContext?.requestCollection(); - }) - .onSubmit(() => { - this.#collectionContext?.requestCollection(); - }) - .observeRouteBuilder((routeBuilder) => { - this.#routeBuilder = routeBuilder; - - // NOTE: Configuring the observations AFTER the route builder is ready, - // otherwise there is a race condition and `#collectionContext.items` tends to win. [LK] - this.#observeCollectionContext(); - }); } #observeCollectionContext() { @@ -128,7 +114,7 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { if (this._userDefinedProperties && this._userDefinedProperties.length > 0) { const userColumns: Array = this._userDefinedProperties.map((item) => { return { - name: item.header, + name: this.localize.string(item.header), alias: item.alias, elementName: item.elementName, labelTemplate: item.nameTemplate, @@ -157,10 +143,13 @@ export class UmbDocumentTableCollectionViewElement extends UmbLitElement { }; } - const editPath = this.#routeBuilder - ? this.#routeBuilder({ entityType: item.entityType }) + - UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }) - : ''; + const editPath = + item.unique && this._workspacePathBuilder + ? this._workspacePathBuilder({ entityType: item.entityType }) + + UMB_EDIT_DOCUMENT_WORKSPACE_PATH_PATTERN.generateLocal({ + unique: item.unique, + }) + : ''; return { columnAlias: column.alias, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts index b31709a246a8..419bd85a626f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts @@ -3,7 +3,7 @@ import type { UmbDocumentCreateOptionsModalData, UmbDocumentCreateOptionsModalValue, } from './document-create-options-modal.token.js'; -import { html, customElement, state, ifDefined, repeat, css, when } from '@umbraco-cms/backoffice/external/lit'; +import { html, customElement, state, repeat, css, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { @@ -19,6 +19,7 @@ import { UMB_CREATE_FROM_BLUEPRINT_DOCUMENT_WORKSPACE_PATH_PATTERN, type UmbDocumentEntityTypeUnion, } from '@umbraco-cms/backoffice/document'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-document-create-options-modal') export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< @@ -101,7 +102,10 @@ export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< this._submitModal(); } - async #onSelectDocumentType(documentTypeUnique: string) { + async #onSelectDocumentType(documentTypeUnique: UmbEntityUnique) { + if (!documentTypeUnique) { + throw new Error('Document type unique is not defined'); + } this.#documentTypeUnique = documentTypeUnique; this.#documentTypeIcon = this._allowedDocumentTypes.find((dt) => dt.unique === documentTypeUnique)?.icon ?? ''; @@ -171,7 +175,6 @@ export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< (documentType) => documentType.unique, (documentType) => html` = [ { @@ -27,8 +24,8 @@ export const manifests: Array = [ match: UMB_DOCUMENT_COLLECTION_ALIAS, }, { - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkCopy, + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_DUPLICATE], }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts index 92cd6d9c18ed..99007be0c4e9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/manifests.ts @@ -1,14 +1,14 @@ import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../collection/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; +import { + UMB_USER_PERMISSION_DOCUMENT_PUBLISH, + UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH, +} from '../user-permissions/constants.js'; import { manifests as duplicateToManifests } from './duplicate-to/manifests.js'; import { manifests as moveToManifests } from './move-to/manifests.js'; import { manifests as trashManifests } from './trash/manifests.js'; -import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; import type { ManifestEntityBulkAction } from '@umbraco-cms/backoffice/extension-registry'; -import { - UMB_COLLECTION_ALIAS_CONDITION, - UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, -} from '@umbraco-cms/backoffice/collection'; export const entityBulkActions: Array = [ { @@ -29,8 +29,8 @@ export const entityBulkActions: Array = [ match: UMB_DOCUMENT_COLLECTION_ALIAS, }, { - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkPublish, + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_PUBLISH], }, ], }, @@ -52,8 +52,8 @@ export const entityBulkActions: Array = [ match: UMB_DOCUMENT_COLLECTION_ALIAS, }, { - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkUnpublish, + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_UNPUBLISH], }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/move-to/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/move-to/manifests.ts index ea65a3c84568..b8f40a919413 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/move-to/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/move-to/manifests.ts @@ -1,14 +1,10 @@ import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import { UMB_DOCUMENT_TREE_ALIAS } from '../../tree/manifests.js'; - +import { UMB_USER_PERMISSION_DOCUMENT_MOVE } from '../../user-permissions/constants.js'; import { UMB_BULK_MOVE_DOCUMENT_REPOSITORY_ALIAS } from './repository/constants.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; -import { - UMB_COLLECTION_ALIAS_CONDITION, - UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, -} from '@umbraco-cms/backoffice/collection'; -import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; export const manifests: Array = [ { @@ -28,8 +24,8 @@ export const manifests: Array = [ match: UMB_DOCUMENT_COLLECTION_ALIAS, }, { - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkMove, + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_MOVE], }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/trash/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/trash/manifests.ts index 4177d0a04b3c..5847243d00c6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/trash/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-bulk-actions/trash/manifests.ts @@ -1,12 +1,9 @@ import { UMB_DOCUMENT_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; +import { UMB_USER_PERMISSION_DOCUMENT_DELETE } from '../../user-permissions/constants.js'; import { UMB_BULK_TRASH_DOCUMENT_REPOSITORY_ALIAS } from './repository/constants.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; -import { - UMB_COLLECTION_ALIAS_CONDITION, - UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, -} from '@umbraco-cms/backoffice/collection'; -import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; export const manifests: Array = [ { @@ -25,8 +22,8 @@ export const manifests: Array = [ match: UMB_DOCUMENT_COLLECTION_ALIAS, }, { - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkDelete, + alias: 'Umb.Condition.UserPermission.Document', + allOf: [UMB_USER_PERMISSION_DOCUMENT_DELETE], }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index 21eb2be78026..4d534aa01041 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -102,9 +102,9 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource { return { - state: variant.state, culture: variant.culture || null, segment: variant.segment || null, + state: variant.state, name: variant.name, publishDate: variant.publishDate || null, createDate: variant.createDate, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 44afaabd8ec4..e77dac186700 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -208,6 +208,19 @@ export class UmbDocumentWorkspaceContext this._data.updateCurrent({ template: { unique: templateUnique } }); } + /** + * Request a submit of the workspace, in the case of Document Workspaces the validation does not need to be valid for this to be submitted. + * @returns {Promise} a promise which resolves once it has been completed. + */ + public override requestSubmit() { + return this._handleSubmit(); + } + + // Because we do not make validation prevent submission this also submits the workspace. [NL] + public override invalidSubmit() { + return this._handleSubmit(); + } + async #handleSaveAndPreview() { const unique = this.getUnique(); if (!unique) throw new Error('Unique is missing'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts index 89904171339c..c3c2f927f6a5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/media-type-structure.server.data-source.ts @@ -1,3 +1,4 @@ +import { UMB_MEDIA_TYPE_ENTITY_TYPE } from '../../entity.js'; import type { UmbAllowedMediaTypeModel } from './types.js'; import { MediaTypeService } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbContentTypeStructureServerDataSourceBase } from '@umbraco-cms/backoffice/content-type'; @@ -38,6 +39,7 @@ const getAllowedChildrenOf = (unique: string | null) => { const mapper = (item: AllowedMediaTypeModel): UmbAllowedMediaTypeModel => { return { unique: item.id, + entityType: UMB_MEDIA_TYPE_ENTITY_TYPE, name: item.name, description: item.description || null, icon: item.icon || null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/types.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/types.ts index bd56e8c3e52a..4f0d1bd5cf79 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/repository/structure/types.ts @@ -1,5 +1,6 @@ -export interface UmbAllowedMediaTypeModel { - unique: string; +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbAllowedMediaTypeModel extends UmbEntityModel { name: string; description: string | null; icon: string | null; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts index 1a14b5ec76a1..8c5a60fcdddd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/action/create-media-collection-action.element.ts @@ -5,11 +5,10 @@ import { UMB_MEDIA_ENTITY_TYPE, UMB_MEDIA_ROOT_ENTITY_TYPE } from '../../entity. import { html, customElement, property, state, map } from '@umbraco-cms/backoffice/external/lit'; import { UmbMediaTypeStructureRepository } from '@umbraco-cms/backoffice/media-type'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import type { ManifestCollectionAction } from '@umbraco-cms/backoffice/collection'; import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; @customElement('umb-create-media-collection-action') export class UmbCreateMediaCollectionActionElement extends UmbLitElement { @@ -17,10 +16,7 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { private _allowedMediaTypes: Array = []; @state() - private _createMediaPath = ''; - - @state() - private _currentView?: string; + private _workspacePathBuilder?: UmbModalRouteBuilder; @state() private _mediaUnique?: UmbEntityUnique; @@ -31,9 +27,6 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { @state() private _popoverOpen = false; - @state() - private _rootPathName?: string; - @property({ attribute: false }) manifest?: ManifestCollectionAction; @@ -42,15 +35,6 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { constructor() { super(); - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath('media') - .onSetup(() => { - return { data: { entityType: 'media', preset: {} } }; - }) - .observeRouteBuilder((routeBuilder) => { - this._createMediaPath = routeBuilder({}); - }); - this.consumeContext(UMB_MEDIA_WORKSPACE_CONTEXT, (workspaceContext) => { this.observe(workspaceContext.unique, (unique) => { this._mediaUnique = unique; @@ -62,11 +46,8 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { }); this.consumeContext(UMB_MEDIA_COLLECTION_CONTEXT, (collectionContext) => { - this.observe(collectionContext.view.currentView, (currentView) => { - this._currentView = currentView?.meta.pathName; - }); - this.observe(collectionContext.view.rootPathName, (rootPathName) => { - this._rootPathName = rootPathName; + this.observe(collectionContext.workspacePathBuilder, (builder) => { + this._workspacePathBuilder = builder; }); }); } @@ -90,14 +71,14 @@ export class UmbCreateMediaCollectionActionElement extends UmbLitElement { } #getCreateUrl(item: UmbAllowedMediaTypeModel) { - return ( - this._createMediaPath.replace(`${this._rootPathName}`, `${this._rootPathName}/${this._currentView}`) + - UMB_CREATE_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ - parentEntityType: this._mediaUnique ? UMB_MEDIA_ENTITY_TYPE : UMB_MEDIA_ROOT_ENTITY_TYPE, - parentUnique: this._mediaUnique ?? 'null', - mediaTypeUnique: item.unique, - }) - ); + return item.unique && this._workspacePathBuilder + ? this._workspacePathBuilder({ entityType: UMB_MEDIA_ENTITY_TYPE }) + + UMB_CREATE_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ + parentEntityType: this._mediaUnique ? UMB_MEDIA_ENTITY_TYPE : UMB_MEDIA_ROOT_ENTITY_TYPE, + parentUnique: this._mediaUnique ?? 'null', + mediaTypeUnique: item.unique, + }) + : ''; } override render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts index f6fff936ba28..c4a1b63f5693 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/grid/media-grid-collection-view.element.ts @@ -7,15 +7,14 @@ import { UMB_MEDIA_PLACEHOLDER_ENTITY_TYPE } from '../../../entity.js'; import { css, customElement, html, ifDefined, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; import '@umbraco-cms/backoffice/imaging'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; @customElement('umb-media-grid-collection-view') export class UmbMediaGridCollectionViewElement extends UmbLitElement { @state() - private _editMediaPath = ''; + private _workspacePathBuilder?: UmbModalRouteBuilder; @state() private _items: Array = []; @@ -29,23 +28,16 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { super(); this.consumeContext(UMB_MEDIA_COLLECTION_CONTEXT, (collectionContext) => { this.#collectionContext = collectionContext; + collectionContext.setupView(this); + this.observe( + collectionContext.workspacePathBuilder, + (builder) => { + this._workspacePathBuilder = builder; + }, + 'observePath', + ); this.#observeCollectionContext(); }); - - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath('media') - .onSetup(() => { - return { data: { entityType: 'media', preset: {} } }; - }) - .onReject(() => { - this.#collectionContext?.requestCollection(); - }) - .onSubmit(() => { - this.#collectionContext?.requestCollection(); - }) - .observeRouteBuilder((routeBuilder) => { - this._editMediaPath = routeBuilder({}); - }); } #observeCollectionContext() { @@ -60,14 +52,6 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { ); } - #onOpen(event: Event, unique: string) { - event.preventDefault(); - event.stopPropagation(); - - const url = this._editMediaPath + UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ unique }); - window.history.pushState(null, '', url); - } - #onSelect(item: UmbMediaCollectionItemModel) { if (item.unique) { this.#collectionContext?.selection.select(item.unique); @@ -84,6 +68,15 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { return this.#collectionContext?.selection.isSelected(item.unique); } + #getEditUrl(item: UmbMediaCollectionItemModel) { + return item.unique && this._workspacePathBuilder + ? this._workspacePathBuilder({ entityType: item.entityType }) + + UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ + unique: item.unique, + }) + : ''; + } + override render() { return html`
    @@ -106,7 +99,7 @@ export class UmbMediaGridCollectionViewElement extends UmbLitElement { selectable ?select-only=${this._selection && this._selection.length > 0} ?selected=${this.#isSelected(item)} - @open=${(event: Event) => this.#onOpen(event, item.unique)} + href=${this.#getEditUrl(item)} @selected=${() => this.#onSelect(item)} @deselected=${() => this.#onDeselect(item)} class="media-item"> diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts index 19f90522ed9a..7e1206cb2e05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/collection/views/table/media-table-collection-view.element.ts @@ -14,16 +14,18 @@ import type { UmbTableOrderedEvent, UmbTableSelectedEvent, } from '@umbraco-cms/backoffice/components'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; -import { UmbModalRouteRegistrationController, type UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; import './column-layouts/media-table-column-name.element.js'; +import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; @customElement('umb-media-table-collection-view') export class UmbMediaTableCollectionViewElement extends UmbLitElement { @state() private _userDefinedProperties?: Array; + @state() + private _workspacePathBuilder?: UmbModalRouteBuilder; + @state() private _items?: Array; @@ -52,36 +54,20 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { #collectionContext?: UmbDefaultCollectionContext; - #routeBuilder?: UmbModalRouteBuilder; - constructor() { super(); this.consumeContext(UMB_MEDIA_COLLECTION_CONTEXT, (collectionContext) => { this.#collectionContext = collectionContext; + this.#observeCollectionContext(); + collectionContext.setupView(this); + this.observe( + collectionContext.workspacePathBuilder, + (builder) => { + this._workspacePathBuilder = builder; + }, + 'observePath', + ); }); - - this.#registerModalRoute(); - } - - #registerModalRoute() { - new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) - .addAdditionalPath(':entityType') - .onSetup((params) => { - return { data: { entityType: params.entityType, preset: {} } }; - }) - .onReject(() => { - this.#collectionContext?.requestCollection(); - }) - .onSubmit(() => { - this.#collectionContext?.requestCollection(); - }) - .observeRouteBuilder((routeBuilder) => { - this.#routeBuilder = routeBuilder; - - // NOTE: Configuring the observations AFTER the route builder is ready, - // otherwise there is a race condition and `#collectionContext.items` tends to win. [LK] - this.#observeCollectionContext(); - }); } #observeCollectionContext() { @@ -118,7 +104,7 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { if (this._userDefinedProperties && this._userDefinedProperties.length > 0) { const userColumns: Array = this._userDefinedProperties.map((item) => { return { - name: item.header, + name: this.localize.string(item.header), alias: item.alias, elementName: item.elementName, allowSorting: true, @@ -152,10 +138,13 @@ export class UmbMediaTableCollectionViewElement extends UmbLitElement { }; } - const editPath = this.#routeBuilder - ? this.#routeBuilder({ entityType: item.entityType }) + - UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ unique: item.unique }) - : ''; + const editPath = + item.unique && this._workspacePathBuilder + ? this._workspacePathBuilder({ entityType: item.entityType }) + + UMB_EDIT_MEDIA_WORKSPACE_PATH_PATTERN.generateLocal({ + unique: item.unique, + }) + : ''; return { columnAlias: column.alias, diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts similarity index 59% rename from src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts index d4ecb3632f4d..0bfa3c6797d4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/manifests.ts @@ -1,14 +1,13 @@ export const manifests: Array = [ { - type: 'sectionView', - alias: 'Umb.SectionView.Media', - name: 'Media Section View', - element: () => import('./media-section-view.element.js'), + type: 'dashboard', + alias: 'Umb.Dashboard.Media', + name: 'Media Dashboard', + element: () => import('./media-dashboard.element.js'), weight: 200, meta: { label: '#general_media', pathname: 'media', - icon: 'icon-user', }, conditions: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/media-section-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/media-dashboard.element.ts similarity index 84% rename from src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/media-section-view.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/media-dashboard.element.ts index c4effb5844d6..26e04890a914 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/section-view/media-section-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dashboard/media-dashboard.element.ts @@ -1,21 +1,18 @@ import { UmbMediaCollectionRepository } from '../collection/repository/index.js'; import { UMB_MEDIA_COLLECTION_ALIAS } from '../collection/index.js'; -import { UMB_MEDIA_ENTITY_TYPE } from '../entity.js'; +import { UMB_MEDIA_ROOT_ENTITY_TYPE } from '../entity.js'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; -import { UmbCollectionElement } from '@umbraco-cms/backoffice/collection'; import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type'; import { UmbEntityContext } from '@umbraco-cms/backoffice/entity'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; -import type { - UmbCollectionBulkActionPermissions, - UmbCollectionConfiguration, -} from '@umbraco-cms/backoffice/collection'; +import { UmbCollectionElement } from '@umbraco-cms/backoffice/collection'; +import type { UmbCollectionConfiguration } from '@umbraco-cms/backoffice/collection'; import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type'; import type { UmbRoute } from '@umbraco-cms/backoffice/router'; -@customElement('umb-media-section-view') -export class UmbMediaSectionViewElement extends UmbLitElement { +@customElement('umb-media-dashboard') +export class UmbMediaDashboardElement extends UmbLitElement { #dataTypeDetailRepository = new UmbDataTypeDetailRepository(this); #entityContext = new UmbEntityContext(this); #mediaCollectionRepository = new UmbMediaCollectionRepository(this); @@ -28,7 +25,7 @@ export class UmbMediaSectionViewElement extends UmbLitElement { this.#defineRoutes(); - this.#entityContext.setEntityType(UMB_MEDIA_ENTITY_TYPE); + this.#entityContext.setEntityType(UMB_MEDIA_ROOT_ENTITY_TYPE); this.#entityContext.setUnique(null); } @@ -74,7 +71,6 @@ export class UmbMediaSectionViewElement extends UmbLitElement { return { unique: '', dataTypeId: '', - allowedEntityBulkActions: config?.getValueByAlias('bulkActionPermissions'), layouts: config?.getValueByAlias('layouts'), orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate', orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc', @@ -101,10 +97,10 @@ export class UmbMediaSectionViewElement extends UmbLitElement { ]; } -export default UmbMediaSectionViewElement; +export default UmbMediaDashboardElement; declare global { interface HTMLElementTagNameMap { - 'umb-media-section-view': UmbMediaSectionViewElement; + 'umb-media-dashboard': UmbMediaDashboardElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts index cce306a0bf78..a598d96e6030 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/dropzone-manager.class.ts @@ -157,6 +157,10 @@ export class UmbDropzoneManager extends UmbControllerBase { const mediaTypeUnique = options[0].unique; + if (!mediaTypeUnique) { + throw new Error('Media type unique is not defined'); + } + // Handle files and folders differently: a file is uploaded as temp then created as a media item, and a folder is created as a media item directly if (item.temporaryFile) { await this.#handleFile(item as UmbUploadableFile, mediaTypeUnique); diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts index 4e6b57c60817..6e4c7fcd9750 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/dropzone/modals/dropzone-media-type-picker/dropzone-media-type-picker-modal.element.ts @@ -7,6 +7,7 @@ import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; import type { UUIButtonElement } from '@umbraco-cms/backoffice/external/uui'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-dropzone-media-type-picker-modal') export class UmbDropzoneMediaTypePickerModalElement extends UmbModalBaseElement< @@ -25,19 +26,22 @@ export class UmbDropzoneMediaTypePickerModalElement extends UmbModalBaseElement< requestAnimationFrame(() => this._buttonAuto.focus()); } - #onMediaTypePick(unique: string | undefined) { + #onAutoPick() { + this.value = { mediaTypeUnique: undefined }; + this._submitModal(); + } + + #onMediaTypePick(unique: UmbEntityUnique) { + if (!unique) { + throw new Error('Invalid media type unique'); + } this.value = { mediaTypeUnique: unique }; this._submitModal(); } override render() { return html`
    - this.#onMediaTypePick(undefined)} - label="Automatically" - compact> + this.#onAutoPick()} label="Automatically" compact> Auto pick ${repeat( diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts index 8de7a5fef347..f25c1b5759b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-actions/create/media-create-options-modal.element.ts @@ -3,16 +3,7 @@ import type { UmbMediaCreateOptionsModalData, UmbMediaCreateOptionsModalValue, } from './media-create-options-modal.token.js'; -import { - html, - nothing, - customElement, - state, - ifDefined, - repeat, - css, - when, -} from '@umbraco-cms/backoffice/external/lit'; +import { html, nothing, customElement, state, repeat, css, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; import { UmbMediaTypeStructureRepository, type UmbAllowedMediaTypeModel } from '@umbraco-cms/backoffice/media-type'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -60,8 +51,9 @@ export class UmbMediaCreateOptionsModalElement extends UmbModalBaseElement< } } - // close the modal when navigating to data type + // close the modal when navigating to media #onNavigate(mediaType: UmbAllowedMediaTypeModel) { + // TODO: Use a URL builder instead of hardcoding the URL. [NL] const url = `section/media/workspace/media/create/parent/${this.data?.parent.entityType}/${ this.data?.parent.unique ?? 'null' }/${mediaType.unique}`; @@ -110,7 +102,6 @@ export class UmbMediaCreateOptionsModalElement extends UmbModalBaseElement< (mediaType) => mediaType.unique, (mediaType) => html` permissions.allowBulkMove, - }, ], }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-bulk-actions/trash/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-bulk-actions/trash/manifests.ts index 3576dca0339a..9b22d8471983 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-bulk-actions/trash/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/entity-bulk-actions/trash/manifests.ts @@ -2,11 +2,7 @@ import { UMB_MEDIA_COLLECTION_ALIAS } from '../../collection/index.js'; import { UMB_MEDIA_ENTITY_TYPE } from '../../entity.js'; import { UMB_BULK_TRASH_MEDIA_REPOSITORY_ALIAS } from './repository/constants.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; -import { - UMB_COLLECTION_ALIAS_CONDITION, - UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, -} from '@umbraco-cms/backoffice/collection'; -import type { UmbCollectionBulkActionPermissions } from '@umbraco-cms/backoffice/collection'; +import { UMB_COLLECTION_ALIAS_CONDITION } from '@umbraco-cms/backoffice/collection'; const bulkTrashAction: UmbExtensionManifest = { type: 'entityBulkAction', @@ -23,10 +19,6 @@ const bulkTrashAction: UmbExtensionManifest = { alias: UMB_COLLECTION_ALIAS_CONDITION, match: UMB_MEDIA_COLLECTION_ALIAS, }, - { - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - match: (permissions: UmbCollectionBulkActionPermissions) => permissions.allowBulkDelete, - }, ], }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts index 57bb6b4be083..d6f8ad84e405 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts @@ -8,7 +8,7 @@ import { manifests as propertyEditorsManifests } from './property-editors/manife import { manifests as recycleBinManifests } from './recycle-bin/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; -import { manifests as sectionViewManifests } from './section-view/manifests.js'; +import { manifests as sectionViewManifests } from './dashboard/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as fileUploadPreviewManifests } from './components/input-upload-field/manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/manifests.ts index 7c077b7378a1..62a6814b7013 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/manifests.ts @@ -1,5 +1,11 @@ import { manifests as detailManifests } from './detail/manifests.js'; import { manifests as itemManifests } from './item/manifests.js'; import { manifests as urlManifests } from './url/manifests.js'; +import { manifests as validationManifests } from './validation/manifests.js'; -export const manifests: Array = [...detailManifests, ...itemManifests, ...urlManifests]; +export const manifests: Array = [ + ...detailManifests, + ...itemManifests, + ...urlManifests, + ...validationManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/index.ts new file mode 100644 index 000000000000..9fbf43afefd5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/index.ts @@ -0,0 +1,2 @@ +export { UmbMediaValidationRepository as UmbDocumentValidationRepository } from './media-validation.repository.js'; +export { UMB_MEDIA_VALIDATION_REPOSITORY_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/manifests.ts new file mode 100644 index 000000000000..1aea149f16fe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/manifests.ts @@ -0,0 +1,10 @@ +export const UMB_MEDIA_VALIDATION_REPOSITORY_ALIAS = 'Umb.Repository.Document.Validation'; + +export const manifests: Array = [ + { + type: 'repository', + alias: UMB_MEDIA_VALIDATION_REPOSITORY_ALIAS, + name: 'Media Validation Repository', + api: () => import('./media-validation.repository.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.repository.ts new file mode 100644 index 000000000000..85faf65a999a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.repository.ts @@ -0,0 +1,44 @@ +import type { UmbMediaDetailModel } from '../../types.js'; +import { UmbMediaValidationServerDataSource } from './media-validation.server.data-source.js'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbRepositoryBase } from '@umbraco-cms/backoffice/repository'; + +type DetailModelType = UmbMediaDetailModel; + +export class UmbMediaValidationRepository extends UmbRepositoryBase { + #validationDataSource: UmbMediaValidationServerDataSource; + + constructor(host: UmbControllerHost) { + super(host); + + this.#validationDataSource = new UmbMediaValidationServerDataSource(this); + } + + /** + * Returns a promise with an observable of the detail for the given unique + * @param {DetailModelType} model - The model to validate + * @param {string | null} [parentUnique] - The parent unique + * @returns {*} + */ + async validateCreate(model: DetailModelType, parentUnique: string | null) { + if (!model) throw new Error('Data is missing'); + + return this.#validationDataSource.validateCreate(model, parentUnique); + } + + /** + * Saves the given data + * @param {DetailModelType} model - The model to save + * @param {Array} variantIds - The variant ids to save + * @returns {*} + */ + async validateSave(model: DetailModelType, variantIds: Array) { + if (!model) throw new Error('Data is missing'); + if (!model.unique) throw new Error('Unique is missing'); + + return this.#validationDataSource.validateUpdate(model, variantIds); + } +} + +export { UmbMediaValidationRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts new file mode 100644 index 000000000000..5c2737af08a3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/repository/validation/media-validation.server.data-source.ts @@ -0,0 +1,87 @@ +import type { UmbMediaDetailModel } from '../../types.js'; +import { + type CreateMediaRequestModel, + MediaService, + type UpdateMediaRequestModel, +} from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecute } from '@umbraco-cms/backoffice/resources'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; + +/** + * A server data source for Document Validation + * @class UmbDocumentPublishingServerDataSource + * @implements {DocumentTreeDataSource} + */ +export class UmbMediaValidationServerDataSource { + //#host: UmbControllerHost; + + /** + * Creates an instance of UmbDocumentPublishingServerDataSource. + * @param {UmbControllerHost} host - The controller host for this controller to be appended to + * @memberof UmbDocumentPublishingServerDataSource + */ + // TODO: [v15]: ignoring unused var here here to prevent a breaking change + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(host: UmbControllerHost) { + //this.#host = host; + } + + /** + * Validate a new Media on the server + * @param {UmbMediaDetailModel} model - Media Model + * @param {UmbEntityUnique} parentUnique - Parent Unique + * @returns {*} + */ + async validateCreate(model: UmbMediaDetailModel, parentUnique: UmbEntityUnique = null) { + if (!model) throw new Error('Media is missing'); + if (!model.unique) throw new Error('Media unique is missing'); + if (parentUnique === undefined) throw new Error('Parent unique is missing'); + + // TODO: make data mapper to prevent errors + const requestBody: CreateMediaRequestModel = { + id: model.unique, + parent: parentUnique ? { id: parentUnique } : null, + mediaType: { id: model.mediaType.unique }, + values: model.values, + variants: model.variants, + }; + + // Maybe use: tryExecuteAndNotify + return tryExecute( + //this.#host, + MediaService.postMediaValidate({ + requestBody, + }), + ); + } + + /** + * Validate a existing Media + * @param {UmbMediaDetailModel} model - Media Model + * @param {Array} variantIds - Variant Ids + * @returns {Promise<*>} - The response from the server + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async validateUpdate(model: UmbMediaDetailModel, variantIds: Array) { + if (!model.unique) throw new Error('Unique is missing'); + + //const cultures = variantIds.map((id) => id.culture).filter((culture) => culture !== null) as Array; + + // TODO: make data mapper to prevent errors + const requestBody: UpdateMediaRequestModel = { + values: model.values, + variants: model.variants, + }; + + // Maybe use: tryExecuteAndNotify + return tryExecute( + //this.#host, + MediaService.putMediaByIdValidate({ + id: model.unique, + requestBody, + }), + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts index 4da0e7161a5a..5efc63199dc2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts @@ -25,7 +25,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement; + private _allowedMediaTypeUniques?: Array; @query('umb-input-document') private _documentPickerElement?: UmbInputDocumentElement; @@ -41,7 +41,8 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement x.unique).filter((x) => !isUmbracoFolder(x)) ?? []; + this._allowedMediaTypeUniques = + (mediaTypes?.items.map((x) => x.unique).filter((x) => x && !isUmbracoFolder(x)) as Array) ?? []; } #partialUpdateLink(linkObject: Partial) { @@ -255,7 +256,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement this.#onPickerSelection(e, 'media')}> diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/manifests.ts index 1b13c57051e4..2d25db447551 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/manifests.ts @@ -1,5 +1,6 @@ import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/property-editor'; +/** @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ export const manifest: ManifestPropertyEditorUi = { type: 'propertyEditorUi', alias: 'Umb.PropertyEditorUi.Collection.BulkActionPermissions', diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts index f18b07d7e304..d08b04b423a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/config/bulk-action-permissions/permissions.element.ts @@ -18,6 +18,7 @@ type BulkActionPermissionType = /** * @element umb-property-editor-ui-collection-permissions + * @deprecated No longer used internally. This will be removed in Umbraco 17. [LK] */ @customElement('umb-property-editor-ui-collection-permissions') export class UmbPropertyEditorUICollectionPermissionsElement @@ -66,6 +67,12 @@ export class UmbPropertyEditorUICollectionPermissionsElement this.dispatchEvent(new UmbPropertyValueChangeEvent()); } + protected override firstUpdated() { + console.warn( + 'The `umb-property-editor-ui-collection-permissions` component has been deprecated, it will be removed in Umbraco 17.', + ); + } + override render() { return html`>('includeProperties'), (includeProperties) => { if (!includeProperties) return; - this._options = includeProperties.map((property) => ({ + const options = includeProperties.map((property) => ({ name: property.header, value: property.alias, selected: property.alias === this.value, })); + this._options = [ + { name: this.localize.term('general_name'), value: 'name', selected: 'name' === this.value }, + ...options, + ]; }, '_observeIncludeProperties', ); diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts index eb32c743c8e7..bad182f4ba00 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/manifests.ts @@ -41,12 +41,6 @@ const propertyEditorUiManifest: ManifestPropertyEditorUi = { propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer', config: [{ alias: 'min', value: 0 }], }, - { - alias: 'bulkActionPermissions', - label: 'Bulk Action Permissions', - description: 'The bulk actions that are allowed on items in the Collection view.', - propertyEditorUiAlias: 'Umb.PropertyEditorUi.Collection.BulkActionPermissions', - }, { alias: 'icon', label: 'Workspace View icon', @@ -85,10 +79,6 @@ const propertyEditorUiManifest: ManifestPropertyEditorUi = { { alias: 'pageSize', value: 10 }, { alias: 'orderBy', value: 'sortOrder' }, { alias: 'orderDirection', value: 'desc' }, - { - alias: 'bulkActionPermissions', - value: { allowBulkPublish: true, allowBulkUnpublish: true, allowBulkCopy: true }, - }, { alias: 'icon', value: 'icon-list' }, ], }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection.element.ts index 61e17dd255d9..9f85591d2fd0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/collection/property-editor-ui-collection.element.ts @@ -7,10 +7,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_DOCUMENT_COLLECTION_ALIAS } from '@umbraco-cms/backoffice/document'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import { UMB_CONTENT_COLLECTION_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content'; -import type { - UmbCollectionBulkActionPermissions, - UmbCollectionConfiguration, -} from '@umbraco-cms/backoffice/collection'; +import type { UmbCollectionConfiguration } from '@umbraco-cms/backoffice/collection'; /** * @element umb-property-editor-ui-collection @@ -58,7 +55,6 @@ export class UmbPropertyEditorUICollectionElement extends UmbLitElement implemen ): UmbCollectionConfiguration { const pageSize = Number(config?.getValueByAlias('pageSize')); return { - allowedEntityBulkActions: config?.getValueByAlias('bulkActionPermissions'), layouts: config?.getValueByAlias('layouts'), orderBy: config?.getValueByAlias('orderBy') ?? 'updateDate', orderDirection: config?.getValueByAlias('orderDirection') ?? 'asc', diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.element.ts index 93b72068508a..3e117b0fa2ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/welcome/settings-welcome-dashboard.element.ts @@ -7,8 +7,7 @@ export class UmbSettingsWelcomeDashboardElement extends UmbLitElement { override render() { return html`
    - -

    Documentation

    +

    Read more about working with the items in Settings in our Documentation. @@ -16,39 +15,32 @@ export class UmbSettingsWelcomeDashboardElement extends UmbLitElement {

    + target="_blank">
    - -

    Community

    +

    Ask a question in the community forum or our Discord community

    -
    + target="_blank"> + target="_blank">
    - -

    Training

    - +

    Find out about real-life training and certification opportunities @@ -58,13 +50,10 @@ export class UmbSettingsWelcomeDashboardElement extends UmbLitElement { look="primary" href="https://umbraco.com/training/" label=${this.localize.term('settingsDashboard_getCertified')} - target="_blank" - rel="noopener"> + target="_blank"> - -

    Support

    - +

    Extend your team with a highly skilled and passionate bunch of Umbraco know-it-alls @@ -74,12 +63,10 @@ export class UmbSettingsWelcomeDashboardElement extends UmbLitElement { look="primary" href="https://umbraco.com/support/" label=${this.localize.term('settingsDashboard_getHelp')} - target="_blank" - rel="noopener"> + target="_blank"> - -

    Videos

    +

    Watch our free tutorial videos on the Umbraco Learning Base YouTube channel, to get upto speed quickly @@ -90,8 +77,7 @@ export class UmbSettingsWelcomeDashboardElement extends UmbLitElement { look="primary" href="https://www.youtube.com/c/UmbracoLearningBase" label=${this.localize.term('settingsDashboard_watchVideos')} - target="_blank" - rel="noopener"> + target="_blank">

    `; @@ -107,6 +93,12 @@ export class UmbSettingsWelcomeDashboardElement extends UmbLitElement { padding: var(--uui-size-layout-1); } + uui-box { + p:first-child { + margin-top: 0; + } + } + @media (max-width: 1200px) { #settings-dashboard { grid-template-columns: repeat(2, 1fr); diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts index d406a9dee96a..b0ac1de56837 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/components/input-tiptap/tiptap-toolbar.element.ts @@ -109,6 +109,7 @@ export class UmbTiptapToolbarElement extends UmbLitElement { .row { display: flex; flex-direction: row; + flex-wrap: wrap; .group { display: inline-flex; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts index 4eb24c999526..c5ed932ede72 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/contexts/tiptap-toolbar-configuration.context.ts @@ -180,7 +180,11 @@ export class UmbTiptapToolbarConfigurationContext extends UmbContextBase | null} value - The value to migrate. + * @returns {UmbTiptapToolbarValue} The migrated value. + * @deprecated This will be removed in Umbraco 16. + */ public migrateTinyMceToolbar(value?: UmbTiptapToolbarValue | Array | null): UmbTiptapToolbarValue { if (this.isValidToolbarValue(value)) return value; diff --git a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts index e104bbe2e96c..268abd764923 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/tiptap/property-editors/tiptap/property-editor-ui-tiptap.stories.ts @@ -16,41 +16,29 @@ const config = new UmbPropertyEditorConfigCollection([ alias: 'toolbar', value: [ [ - [ - ['Umb.Tiptap.Toolbar.Bold', 'Umb.Tiptap.Toolbar.Italic', 'Umb.Tiptap.Toolbar.Underline'], - [ - 'Umb.Tiptap.Toolbar.TextAlignLeft', - 'Umb.Tiptap.Toolbar.TextAlignCenter', - 'Umb.Tiptap.Toolbar.TextAlignRight', - ], - ['Umb.Tiptap.Toolbar.Heading1', 'Umb.Tiptap.Toolbar.Heading2', 'Umb.Tiptap.Toolbar.Heading3'], - ['Umb.Tiptap.Toolbar.Unlink', 'Umb.Tiptap.Toolbar.Link'], - ['Umb.Tiptap.Toolbar.Embed', 'Umb.Tiptap.Toolbar.MediaPicker', 'Umb.Tiptap.Toolbar.BlockPicker'], - ['Umb.Tiptap.Toolbar.Redo', 'Umb.Tiptap.Toolbar.Undo'], - ], + ['Umb.Tiptap.Toolbar.SourceEditor'], + ['Umb.Tiptap.Toolbar.Bold', 'Umb.Tiptap.Toolbar.Italic', 'Umb.Tiptap.Toolbar.Underline'], + ['Umb.Tiptap.Toolbar.TextAlignLeft', 'Umb.Tiptap.Toolbar.TextAlignCenter', 'Umb.Tiptap.Toolbar.TextAlignRight'], + ['Umb.Tiptap.Toolbar.BulletList', 'Umb.Tiptap.Toolbar.OrderedList'], + ['Umb.Tiptap.Toolbar.Blockquote', 'Umb.Tiptap.Toolbar.HorizontalRule'], + ['Umb.Tiptap.Toolbar.Link', 'Umb.Tiptap.Toolbar.Unlink'], + ['Umb.Tiptap.Toolbar.MediaPicker', 'Umb.Tiptap.Toolbar.EmbeddedMedia'], ], ], }, { alias: 'extensions', value: [ - 'Umb.Tiptap.Bold', - 'Umb.Tiptap.Italic', - 'Umb.Tiptap.Underline', - 'Umb.Tiptap.Strike', - 'Umb.Tiptap.Blockquote', - 'Umb.Tiptap.CodeBlock', - 'Umb.Tiptap.HorizontalRule', - 'Umb.Tiptap.Figure', - 'Umb.Tiptap.Table', - 'Umb.Tiptap.Link', 'Umb.Tiptap.Embed', + 'Umb.Tiptap.Link', + 'Umb.Tiptap.Figure', 'Umb.Tiptap.Image', - 'Umb.Tiptap.Heading', - 'Umb.Tiptap.List', + 'Umb.Tiptap.Subscript', + 'Umb.Tiptap.Superscript', + 'Umb.Tiptap.Table', + 'Umb.Tiptap.Underline', 'Umb.Tiptap.TextAlign', 'Umb.Tiptap.MediaUpload', - 'Umb.Tiptap.Block', ], }, ]); diff --git a/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml b/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml index 72693f113d23..f3a15e71542e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml +++ b/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml @@ -11,8 +11,6 @@ - - diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index e1a56c1f8584..3ca1a953cec1 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.25", - "@umbraco/playwright-testhelpers": "^15.0.0-beta.6", + "@umbraco/playwright-testhelpers": "^15.0.1", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -58,14 +58,16 @@ "version": "2.0.25", "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.25.tgz", "integrity": "sha512-bFO4AuXUlkyRtBolqOnAvlW12B7Zh/cee3DHShAb+KaXdAC9LzvHYCSH33yJRk2Qc9KvK6ECAMamhiBcT1cMWw==", + "license": "MIT", "dependencies": { "camelize": "^1.0.1" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "15.0.0-beta.6", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.0-beta.6.tgz", - "integrity": "sha512-qwvqg3fzBGXqhH25hLwjLeciazE413ee34R6Ejbg/epKoIcxF/WbMN9NNosBbamlXPg5iCKv3nr543YHTGX1Jw==", + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-15.0.1.tgz", + "integrity": "sha512-6ow0oSZ1SqR1ES7yudXjagXvV2C6eOWZoBNLvHAkwMeYtHroaQ44Gn+n+TEgu6S3ipa6cr0LBlrUWL19dpEWMw==", + "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.25", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index e1f07c7c0443..0a6519dfd355 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.25", - "@umbraco/playwright-testhelpers": "^15.0.0-beta.6", + "@umbraco/playwright-testhelpers": "^15.0.1", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts index c0b1d0710713..5f1650811634 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts @@ -8,7 +8,7 @@ export const STORAGE_STATE = path.join(__dirname, 'playwright/.auth/user.json'); export default defineConfig({ testDir: './tests/', /* Maximum time one test can run for. */ - timeout: 40 * 1000, + timeout: 30 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. @@ -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 ? 2 : 1, + retries: 2, // 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/Content/Content.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/Content.spec.ts index 0c7a869eb039..07f16cab9fb6 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/Content.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/Content.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; let documentTypeId = ''; @@ -32,7 +32,7 @@ test('can create empty content', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickSaveButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe(expectedState); @@ -54,6 +54,8 @@ test('can save and publish empty content', {tag: '@smoke'}, async ({umbracoApi, // Assert await umbracoUi.content.doesSuccessNotificationsHaveCount(2); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe(expectedState); @@ -75,7 +77,7 @@ test('can create content', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickSaveButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.values[0].value).toBe(contentText); @@ -96,7 +98,7 @@ test('can rename content', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickSaveButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const updatedContentData = await umbracoApi.document.get(contentId); expect(updatedContentData.variants[0].name).toEqual(contentName); }); @@ -116,7 +118,7 @@ test('can update content', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickSaveButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const updatedContentData = await umbracoApi.document.get(contentId); expect(updatedContentData.variants[0].name).toEqual(contentName); expect(updatedContentData.values[0].value).toBe(contentText); @@ -135,7 +137,7 @@ test('can publish content', async ({umbracoApi, umbracoUi}) => { await umbracoUi.content.clickPublishButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe('Published'); }); @@ -145,8 +147,7 @@ test('can unpublish content', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) = const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName); - const publishData = {"publishSchedules":[{"culture":null}]}; - await umbracoApi.document.publish(contentId, publishData); + await umbracoApi.document.publish(contentId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -156,7 +157,7 @@ test('can unpublish content', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) = await umbracoUi.content.clickConfirmToUnpublishButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.unpublished); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.variants[0].state).toBe('Draft'); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts new file mode 100644 index 000000000000..c13538b73f3a --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts @@ -0,0 +1,317 @@ +import {ConstantHelper, test, AliasHelper} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +let customDataTypeName = ''; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test('can create content with the custom data type with email address property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Email Address'; + const customDataTypeId = await umbracoApi.dataType.createEmailAddressDataType(customDataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can add text to the email address in the content section', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Email Address'; + const emailAddress = 'test@acceptance.test'; + const customDataTypeId = await umbracoApi.dataType.createEmailAddressDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.enterTextstring(emailAddress); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual(emailAddress); +}); + +test('can create content with the custom data type with decimal property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Decimal'; + const customDataTypeId = await umbracoApi.dataType.createDecimalDataType(customDataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can add decimal number to the decimal in the content section', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Decimal'; + const decimal = 3.9; + const customDataTypeId = await umbracoApi.dataType.createDecimalDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.enterNumeric(decimal); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual(decimal); +}); + +// Skip this test as currently there is no code editor property editor available. +test.skip('can create content with the custom data type with code editor property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Code Editor'; + const customDataTypeId = await umbracoApi.dataType.createCodeEditorDataType(customDataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can add javascript code to the code editor in the content section', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Code Editor'; + const javascriptCode = 'const test = \'This is the acceptance test\';'; + const customDataTypeId = await umbracoApi.dataType.createCodeEditorDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.enterCodeEditorValue(javascriptCode); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual(javascriptCode); +}); + +test('can create content with the custom data type with markdown editor property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Markdown Editor'; + const customDataTypeId = await umbracoApi.dataType.createMarkdownEditorDataType(customDataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can add code to the markdown editor in the content section', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Markdown Editor'; + const inputText = '# This is test heading\r\n> This is test quote'; + const customDataTypeId = await umbracoApi.dataType.createMarkdownEditorDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.enterMarkdownEditorValue(inputText); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual(inputText); +}); + +test('can create content with the custom data type with multiple text string property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Multiple Text String'; + const customDataTypeId = await umbracoApi.dataType.createMultipleTextStringDataType(customDataTypeName); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can add string to the multiple text string in the content section', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Multiple Text String'; + const multipleTextStringValue = 'Test text string item'; + const customDataTypeId = await umbracoApi.dataType.createMultipleTextStringDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.addMultipleTextStringItem(multipleTextStringValue); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual([multipleTextStringValue]); +}); + +test('can create content with the custom data type with slider property editor', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Slider'; + const customDataTypeId = await umbracoApi.dataType.createSliderDataTyper(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values).toEqual([]); +}); + +test('can change slider value in the content section', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Slider'; + const sliderValue = 10; + const expectedValue = { + "from": sliderValue, + "to": sliderValue + } + const customDataTypeId = await umbracoApi.dataType.createSliderDataTyper(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.changeSliderValue(sliderValue.toString()); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.values[0].alias).toEqual(AliasHelper.toAlias(customDataTypeName)); + expect(contentData.values[0].value).toEqual(expectedValue); +}); + +test('can save content after changing the property editor of the custom data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + customDataTypeName = 'Custom Text String'; + const inputText = 'Test textstring'; + const customDataTypeId = await umbracoApi.dataType.createTextstringDataType(customDataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + + // Act + // Update the property editor of the custom data type + const customDataTypeData = await umbracoApi.dataType.getByName(customDataTypeName); + customDataTypeData.editorAlias = 'Umbraco.MultipleTextstring'; + customDataTypeData.editorUiAlias = 'Umb.PropertyEditorUi.MultipleTextString'; + await umbracoApi.dataType.update(customDataTypeId, customDataTypeData); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.addMultipleTextStringItem(inputText); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts new file mode 100644 index 000000000000..cc1b17b9a471 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts @@ -0,0 +1,25 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const documentTypeName = 'TestDocumentTypeForContent'; + +test.beforeEach(async ({umbracoApi, umbracoUi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoUi.goToBackOffice(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('cannot create content if allow at root is disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.documentType.createDefaultDocumentType(documentTypeName); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + + // Assert + await umbracoUi.content.isDocumentTypeNameVisible(documentTypeName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts new file mode 100644 index 000000000000..ff8664c33ba5 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts @@ -0,0 +1,128 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const secondLanguageName = 'Danish'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.language.ensureNameNotExists(secondLanguageName); + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.language.ensureNameNotExists(secondLanguageName); +}); + +test('can create content with allow vary by culture enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.documentType.createDocumentTypeWithAllowVaryByCulture(documentTypeName); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); +}); + +test('can create content with names that vary by culture', async ({umbracoApi, umbracoUi}) => { + // Arrange + const danishContentName = 'Test indhold'; + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowVaryByCulture(documentTypeName); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickVariantSelectorButton(); + await umbracoUi.content.clickVariantAddModeButton(); + await umbracoUi.content.enterContentName(danishContentName); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(danishContentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(danishContentName); + expect(contentData.variants.length).toBe(2); + expect(contentData.variants[0].name).toBe(contentName); + expect(contentData.variants[1].name).toBe(danishContentName); +}); + +test('can create content with names that vary by culture and content that is invariant', async ({umbracoApi, umbracoUi}) => { + // Arrange + const danishContentName = 'Test indhold'; + const textContent = 'This is a test text'; + const danishTextContent = 'Dette er testtekst'; + const dataTypeName = 'Textstring'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', false); + await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, textContent, dataTypeName); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickVariantSelectorButton(); + await umbracoUi.content.clickVariantAddModeButton(); + await umbracoUi.content.enterContentName(danishContentName); + await umbracoUi.content.enterTextstring(danishTextContent); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(danishContentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(danishContentName); + expect(contentData.variants.length).toBe(2); + expect(contentData.variants[0].name).toBe(contentName); + expect(contentData.variants[1].name).toBe(danishContentName); + expect(contentData.values.length).toBe(1); + expect(contentData.values[0].value).toBe(danishTextContent); +}); + +test('can create content with names and content that vary by culture', async ({umbracoApi, umbracoUi}) => { + // Arrange + const danishContentName = 'Test indhold'; + const textContent = 'This is a test text'; + const danishTextContent = 'Dette er testtekst'; + const dataTypeName = 'Textstring'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', true); + await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, textContent, dataTypeName, true); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickVariantSelectorButton(); + await umbracoUi.content.clickVariantAddModeButton(); + await umbracoUi.content.enterContentName(danishContentName); + await umbracoUi.content.enterTextstring(danishTextContent); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(danishContentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(danishContentName); + expect(contentData.variants.length).toBe(2); + expect(contentData.variants[0].name).toBe(contentName); + expect(contentData.variants[1].name).toBe(danishContentName); + expect(contentData.values.length).toBe(2); + expect(contentData.values[0].value).toBe(textContent); + expect(contentData.values[1].value).toBe(danishTextContent); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts new file mode 100644 index 000000000000..8ac6b133c8cf --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts @@ -0,0 +1,98 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; + +test.beforeEach(async ({umbracoApi, umbracoUi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoUi.goToBackOffice(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with allowed child node enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeName = 'Test Child Document Type'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(documentTypeName, childDocumentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); +}); + +test('cannot create child content if allowed child node is disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const noAllowedDocumentTypeAvailableMessage = 'There are no allowed Document Types available for creating content here'; + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickCreateButton(); + + // Assert + await umbracoUi.content.isDocumentTypeNameVisible(documentTypeName, false); + await umbracoUi.content.doesModalHaveText(noAllowedDocumentTypeAvailableMessage); +}); + +test('can create multiple child nodes with different document types', async ({umbracoApi, umbracoUi}) => { + // Arrange + const firstChildDocumentTypeName = 'First Child Document Type'; + const secondChildDocumentTypeName = 'Second Child Document Type'; + const firstChildContentName = 'First Child Content'; + const secondChildContentName = 'Second Child Content'; + const firstChildDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(firstChildDocumentTypeName); + const secondChildDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(secondChildDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedTwoChildNodes(documentTypeName, firstChildDocumentTypeId, secondChildDocumentTypeId); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(firstChildContentName, firstChildDocumentTypeId, contentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(secondChildDocumentTypeName); + // This wait is needed + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.enterContentName(secondChildContentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(secondChildContentName)).toBeTruthy(); + const childData = await umbracoApi.document.getChildren(contentId); + expect(childData.length).toBe(2); + expect(childData[0].variants[0].name).toBe(firstChildContentName); + expect(childData[1].variants[0].name).toBe(secondChildContentName); + // verify that the child content displays in the tree after reloading children + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.doesContentTreeHaveName(firstChildContentName); + await umbracoUi.content.doesContentTreeHaveName(secondChildContentName); + + // Clean + await umbracoApi.document.ensureNameNotExists(firstChildContentName); + await umbracoApi.document.ensureNameNotExists(secondChildContentName); + await umbracoApi.documentType.ensureNameNotExists(firstChildDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(secondChildDocumentTypeName); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts new file mode 100644 index 000000000000..d31b884ea146 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts @@ -0,0 +1,66 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const templateName = 'TestTemplate'; +let templateId = ''; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.template.ensureNameNotExists(templateName); + templateId = await umbracoApi.template.createDefaultTemplate(templateName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +test('can create content with an allowed template', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId, true); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.template.id).toBe(templateId); +}); + +test('can create content with multiple allowed templates', async ({umbracoApi, umbracoUi}) => { + // Arrange + const defaultTemplateName = 'TestDefaultTemplate'; + await umbracoApi.template.ensureNameNotExists(defaultTemplateName); + const defaultTemplateId = await umbracoApi.template.createDefaultTemplate(templateName); + await umbracoApi.documentType.createDocumentTypeWithTwoAllowedTemplates(documentTypeName, templateId, defaultTemplateId, true, defaultTemplateId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.template.id).toBe(defaultTemplateId); + + // Clean + await umbracoApi.template.ensureNameNotExists(defaultTemplateName); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts new file mode 100644 index 000000000000..2c369a772bb3 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts @@ -0,0 +1,137 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const childDocumentTypeName = 'TestChildDocumentType'; +const firstChildContentName = 'First Child Content'; +const secondChildContentName = 'Second Child Content'; +const dataTypeName = 'List View - Content'; +let dataTypeData; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content configured as a collection', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.documentType.createDocumentTypeWithCollectionId(documentTypeName, dataTypeData.id); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.isTabNameVisible('Collection'); + await umbracoUi.content.doesContentListHaveNoItemsInList(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); +}); + +test('can create child content in a collection', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedNames = [firstChildContentName]; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndCollectionId(documentTypeName, childDocumentTypeId, dataTypeData.id); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(childDocumentTypeName); + // This wait is needed + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.enterContentName(firstChildContentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + const childData = await umbracoApi.document.getChildren(contentId); + expect(childData.length).toBe(expectedNames.length); + expect(childData[0].variants[0].name).toBe(firstChildContentName); + // verify that the child content displays in collection list after reloading tree + await umbracoUi.waitForTimeout(1000); + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.doesDocumentTableColumnNameValuesMatch(expectedNames); + + // Clean + await umbracoApi.document.ensureNameNotExists(firstChildContentName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); +}); + +test('can create multiple child nodes in a collection', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedNames = [secondChildContentName, firstChildContentName]; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndCollectionId(documentTypeName, childDocumentTypeId, dataTypeData.id); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(firstChildContentName, childDocumentTypeId, contentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(childDocumentTypeName); + // This wait is needed + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.enterContentName(secondChildContentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + const childData = await umbracoApi.document.getChildren(contentId); + expect(childData.length).toBe(expectedNames.length); + expect(childData[0].variants[0].name).toBe(firstChildContentName); + expect(childData[1].variants[0].name).toBe(secondChildContentName); + // verify that the child content displays in collection list after reloading tree + await umbracoUi.waitForTimeout(1000); + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.doesDocumentTableColumnNameValuesMatch(expectedNames); + + // Clean + await umbracoApi.document.ensureNameNotExists(firstChildContentName); + await umbracoApi.document.ensureNameNotExists(secondChildContentName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); +}); + +test('can search in a collection of content', async ({umbracoApi, umbracoUi}) => { + // Arrange + const searchKeyword = 'First'; + const expectedSearchResult = [firstChildContentName]; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndCollectionId(documentTypeName, childDocumentTypeId, dataTypeData.id); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(firstChildContentName, childDocumentTypeId, contentId); + await umbracoApi.document.createDefaultDocumentWithParent(secondChildContentName, childDocumentTypeId, contentId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.searchByKeywordInCollection(searchKeyword); + + // Assert + await umbracoUi.content.doesDocumentTableColumnNameValuesMatch(expectedSearchResult); + + // Clean + await umbracoApi.document.ensureNameNotExists(firstChildContentName); + await umbracoApi.document.ensureNameNotExists(secondChildContentName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts new file mode 100644 index 000000000000..ee86e4eef75f --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts @@ -0,0 +1,393 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'List View - Content Custom'; +const childDocumentTypeName = 'ChildDocumentTypeForContent'; +const childContentName = 'ChildContent'; + +test.beforeEach(async ({umbracoApi, umbracoUi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.dataType.ensureNameNotExists(dataTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); + await umbracoUi.goToBackOffice(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.dataType.ensureNameNotExists(dataTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); +}); + +test('can create content with the list view data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Draft'; + const defaultListViewDataTypeName = 'List View - Content'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(defaultListViewDataTypeName); + await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, defaultListViewDataTypeName, dataTypeData.id, childDocumentTypeId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.variants[0].state).toBe(expectedState); + expect(await umbracoApi.document.getChildrenAmount(contentData.id)).toEqual(0); +}); + +test('can publish content with the list view data type', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Published'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataType(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationsHaveCount(2); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.variants[0].state).toBe(expectedState); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(0); +}); + +test('can create content with a child in the list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataType(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickCreateContentWithName(childDocumentTypeName); + await umbracoUi.content.enterNameInContainer(childContentName); + await umbracoUi.content.clickSaveModalButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); +}); + +test('can publish content with a child in the list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Published'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataType(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickSaveAndPublishButton(); + await umbracoUi.content.goToContentInListViewWithName(childContentName); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationsHaveCount(2); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.variants[0].state).toBe(expectedState); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); + // Checks if child is published + const childContentData = await umbracoApi.document.getByName(childContentName); + expect(childContentData.variants[0].state).toBe(expectedState); +}); + +test('can not publish child in a list when parent is not published', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Draft'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataType(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.goToContentInListViewWithName(childContentName); + await umbracoUi.content.clickContainerSaveAndPublishButton(); + + // Assert + // Content created, but not published + await umbracoUi.content.doesSuccessNotificationsHaveCount(1); + await umbracoUi.content.isErrorNotificationVisible(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.variants[0].state).toBe(expectedState); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); + // Checks if child is still in draft + const childContentData = await umbracoApi.document.getByName(childContentName); + expect(childContentData.variants[0].state).toBe(expectedState); +}); + +test('child is removed from list after child content is deleted', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataType(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); + + // Act + await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.clickActionsMenuForContent(childContentName); + await umbracoUi.content.clickTrashButton(); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.doesContentListHaveNoItemsInList(); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(0); + expect(await umbracoApi.document.doesNameExist(childContentName)).toBeFalsy(); +}); + +test('can sort list by name', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + const secondChildContentName = 'ASecondChildContent'; + await umbracoApi.dataType.createListViewContentDataType(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoApi.document.createDefaultDocumentWithParent(secondChildContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + const childAmountBeforeDelete = await umbracoApi.document.getChildrenAmount(documentId); + expect(childAmountBeforeDelete).toEqual(2); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.clickNameButtonInListView(); + + // Assert + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(2); + await umbracoUi.content.doesFirstItemInListViewHaveName(secondChildContentName); +}); + +test('can publish child content from list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Published'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + const publishData = {"publishSchedules": [{"culture": null}]}; + await umbracoApi.document.publish(documentId, publishData); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.selectContentWithNameInListView(childContentName); + await umbracoUi.content.clickPublishSelectedListItems(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); + const childContentData = await umbracoApi.document.getByName(childContentName); + expect(childContentData.variants[0].state).toBe(expectedState); +}); + +test('can not publish child content from list when parent is not published', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Draft'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.selectContentWithNameInListView(childContentName); + await umbracoUi.content.clickPublishSelectedListItems(); + + // Assert + await umbracoUi.content.isErrorNotificationVisible(); + const childContentData = await umbracoApi.document.getByName(childContentName); + expect(childContentData.variants[0].state).toBe(expectedState); +}); + +test('can unpublish child content from list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedState = 'Draft'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + const childDocumentId = await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + const publishData = {"publishSchedules": [{"culture": null}]}; + await umbracoApi.document.publish(documentId, publishData); + await umbracoApi.document.publish(childDocumentId, publishData); + const childContentDataBeforeUnpublished = await umbracoApi.document.getByName(childContentName); + expect(childContentDataBeforeUnpublished.variants[0].state).toBe('Published'); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.selectContentWithNameInListView(childContentName); + await umbracoUi.content.clickUnpublishSelectedListItems(); + await umbracoUi.content.clickConfirmToUnpublishButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + const childContentData = await umbracoApi.document.getByName(childContentName); + expect(childContentData.variants[0].state).toBe(expectedState); +}); + +test('can duplicate child content in list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const secondDocumentName = 'SecondDocument'; + await umbracoApi.document.ensureNameNotExists(secondDocumentName); + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + const secondDocumentId = await umbracoApi.document.createDefaultDocument(secondDocumentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.selectContentWithNameInListView(childContentName); + await umbracoUi.content.clickDuplicateToSelectedListItems(); + await umbracoUi.content.selectDocumentWithNameAtRoot(secondDocumentName); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesFirstItemInListViewHaveName(childContentName); + await umbracoUi.content.goToContentWithName(secondDocumentName); + await umbracoUi.content.doesFirstItemInListViewHaveName(childContentName); + // Checks firstContentNode + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(1); + // Checks secondContentNode + expect(await umbracoApi.document.getChildrenAmount(secondDocumentId)).toEqual(1); +}); + +test('can move child content in list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const secondDocumentName = 'SecondDocument'; + await umbracoApi.document.ensureNameNotExists(secondDocumentName); + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + const secondDocumentId = await umbracoApi.document.createDefaultDocument(secondDocumentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.selectContentWithNameInListView(childContentName); + await umbracoUi.content.clickMoveToSelectedListItems(); + await umbracoUi.content.selectDocumentWithNameAtRoot(secondDocumentName); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesListViewContainCount(0); + await umbracoUi.content.goToContentWithName(secondDocumentName); + await umbracoUi.content.doesFirstItemInListViewHaveName(childContentName); + // Checks firstContentNode + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(0); + // Checks secondContentNode + expect(await umbracoApi.document.getChildrenAmount(secondDocumentId)).toEqual(1); +}); + +test('can trash child content in list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + + // Act + await umbracoUi.content.selectContentWithNameInListView(childContentName); + await umbracoUi.content.clickTrashSelectedListItems(); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.content.doesListViewContainCount(0); + expect(await umbracoApi.document.getChildrenAmount(documentId)).toEqual(0); + await umbracoUi.content.isItemVisibleInRecycleBin(childContentName); +}); + +test('can search for child content in list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const secondChildName = 'SecondChildDocument'; + await umbracoApi.document.ensureNameNotExists(secondChildName); + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoApi.document.createDefaultDocumentWithParent(secondChildName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.doesListViewContainCount(2); + + // Act + await umbracoUi.content.searchByKeywordInCollection(childContentName); + + // Assert + await umbracoUi.content.doesListViewContainCount(1); + await umbracoUi.content.doesFirstItemInListViewHaveName(childContentName); +}); + +test('can change from list view to grid view in list', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.dataType.createListViewContentDataTypeWithAllPermissions(dataTypeName); + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAPropertyEditorAndAnAllowedChildNode(documentTypeName, dataTypeName, dataTypeData.id, childDocumentTypeId); + const documentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, documentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.isDocumentListViewVisible(); + + // Act + await umbracoUi.content.changeToGridView(); + + // Assert + await umbracoUi.content.isDocumentGridViewVisible(); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMemberPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMemberPicker.spec.ts index 31d51ffdfdad..62eec89659e4 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMemberPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMemberPicker.spec.ts @@ -22,7 +22,7 @@ test.beforeEach(async ({umbracoApi, umbracoUi}) => { test.afterEach(async ({umbracoApi}) => { await umbracoApi.memberType.ensureNameNotExists(memberTypeName); await umbracoApi.member.ensureNameNotExists(memberName); - await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.document.ensureNameNotExists(contentName); await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); @@ -64,7 +64,7 @@ test('can publish content with the member picker data type', async ({umbracoApi, await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.clickChooseMemberPickerButton(); await umbracoUi.content.selectMemberByName(memberName); - await umbracoUi.content.clickChooseModalButton(); + await umbracoUi.content.clickChooseContainerButton(); await umbracoUi.content.clickSaveAndPublishButton(); // Assert @@ -93,4 +93,4 @@ test('can remove a member picker in the content', async ({umbracoApi, umbracoUi} expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); const contentData = await umbracoApi.document.getByName(contentName); expect(contentData.values).toEqual([]); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMultiURLPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMultiURLPicker.spec.ts index da3d8ddc7619..f713427c3ed8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMultiURLPicker.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithMultiURLPicker.spec.ts @@ -13,12 +13,11 @@ test.beforeEach(async ({umbracoApi}) => { }); test.afterEach(async ({umbracoApi}) => { - await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.document.ensureNameNotExists(contentName); await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); -// TODO: Remove skip when the front-end is ready. Currently it is impossible to link an unpublish document -test.skip('can create content with the document link', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { +test('can create content with the document link', {tag: '@smoke'}, async ({page, umbracoApi, umbracoUi}) => { // Arrange const expectedState = 'Draft'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); @@ -28,6 +27,8 @@ test.skip('can create content with the document link', {tag: '@smoke'}, async ({ const documentTypeForLinkedDocumentId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeForLinkedDocumentName); const linkedDocumentName = 'LinkedDocument'; const linkedDocumentId = await umbracoApi.document.createDefaultDocument(linkedDocumentName, documentTypeForLinkedDocumentId); + await umbracoUi.waitForTimeout(2000); + await umbracoApi.document.publish(linkedDocumentId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -39,7 +40,7 @@ test.skip('can create content with the document link', {tag: '@smoke'}, async ({ await umbracoUi.content.clickAddMultiURLPickerButton(); await umbracoUi.content.clickLinkToDocumentButton(); await umbracoUi.content.selectLinkByName(linkedDocumentName); - await umbracoUi.content.clickChooseModalButton(); + await umbracoUi.content.clickButtonWithName('Choose'); await umbracoUi.content.clickSubmitButton(); await umbracoUi.content.clickSaveButton(); @@ -62,8 +63,7 @@ test.skip('can create content with the document link', {tag: '@smoke'}, async ({ await umbracoApi.document.ensureNameNotExists(linkedDocumentName); }); -// TODO: Remove skip when the front-end is ready. Currently it is impossible to link an unpublish document -test.skip('can publish content with the document link', async ({umbracoApi, umbracoUi}) => { +test('can publish content with the document link', async ({umbracoApi, umbracoUi}) => { // Arrange const expectedState = 'Published'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); @@ -74,6 +74,8 @@ test.skip('can publish content with the document link', async ({umbracoApi, umbr const documentTypeForLinkedDocumentId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeForLinkedDocumentName); const linkedDocumentName = 'ContentToPick'; const linkedDocumentId = await umbracoApi.document.createDefaultDocument(linkedDocumentName, documentTypeForLinkedDocumentId); + await umbracoUi.waitForTimeout(2000); + await umbracoApi.document.publish(linkedDocumentId); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -82,7 +84,7 @@ test.skip('can publish content with the document link', async ({umbracoApi, umbr await umbracoUi.content.clickAddMultiURLPickerButton(); await umbracoUi.content.clickLinkToDocumentButton(); await umbracoUi.content.selectLinkByName(linkedDocumentName); - await umbracoUi.content.clickChooseModalButton(); + await umbracoUi.content.clickButtonWithName('Choose'); await umbracoUi.content.clickSubmitButton(); await umbracoUi.content.clickSaveAndPublishButton(); @@ -133,8 +135,7 @@ test('can create content with the external link', async ({umbracoApi, umbracoUi} expect(contentData.values[0].value[0].url).toEqual(link); }); -// TODO: Remove skip when the code is updated due to UI changes -test.skip('can create content with the media link', async ({umbracoApi, umbracoUi}) => { +test('can create content with the media link', async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); @@ -149,7 +150,10 @@ test.skip('can create content with the media link', async ({umbracoApi, umbracoU // Act await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.clickAddMultiURLPickerButton(); - await umbracoUi.content.selectLinkByName(mediaFileName); + await umbracoUi.content.clickLinkToMediaButton(); + await umbracoUi.content.selectMediaWithName(mediaFileName); + await umbracoUi.content.clickMediaPickerModalSubmitButton(); + await umbracoUi.waitForTimeout(500); await umbracoUi.content.clickSubmitButton(); await umbracoUi.content.clickSaveButton(); @@ -169,8 +173,7 @@ test.skip('can create content with the media link', async ({umbracoApi, umbracoU await umbracoApi.media.ensureNameNotExists(mediaFileName); }); -// TODO: Remove skip when the code is updated due to UI changes -test.skip('can add multiple links in the content', async ({umbracoApi, umbracoUi}) => { +test('can add multiple links in the content', async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id); @@ -186,7 +189,10 @@ test.skip('can add multiple links in the content', async ({umbracoApi, umbracoUi await umbracoUi.content.goToContentWithName(contentName); // Add media link await umbracoUi.content.clickAddMultiURLPickerButton(); - await umbracoUi.content.selectLinkByName(mediaFileName); + await umbracoUi.content.clickLinkToMediaButton(); + await umbracoUi.content.selectMediaWithName(mediaFileName); + await umbracoUi.content.clickMediaPickerModalSubmitButton(); + await umbracoUi.waitForTimeout(500); await umbracoUi.content.clickSubmitButton(); // Add external link await umbracoUi.content.clickAddMultiURLPickerButton(); @@ -224,7 +230,7 @@ test('can remove the URL picker in the content', async ({umbracoApi, umbracoUi}) await umbracoApi.document.createDocumentWithExternalLinkURLPicker(contentName, documentTypeId, link, linkTitle); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); - + // Act await umbracoUi.content.goToContentWithName(contentName); await umbracoUi.content.removeUrlPickerByName(linkTitle); @@ -237,8 +243,7 @@ test('can remove the URL picker in the content', async ({umbracoApi, umbracoUi}) expect(contentData.values).toEqual([]); }); -// TODO: Remove skip when the code is updated due to UI changes -test.skip('can edit the URL picker in the content', async ({umbracoApi, umbracoUi}) => { +test('can edit the URL picker in the content', async ({umbracoApi, umbracoUi}) => { // Arrange const updatedLinkTitle = 'Updated Umbraco Documentation'; const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); @@ -246,10 +251,10 @@ test.skip('can edit the URL picker in the content', async ({umbracoApi, umbracoU await umbracoApi.document.createDocumentWithExternalLinkURLPicker(contentName, documentTypeId, link, linkTitle); await umbracoUi.goToBackOffice(); await umbracoUi.content.goToSection(ConstantHelper.sections.content); - + // Act await umbracoUi.content.goToContentWithName(contentName); - await umbracoUi.content.clickEditUrlPickerButtonByName(linkTitle); + await umbracoUi.content.clickLinkWithName(linkTitle); await umbracoUi.content.enterLinkTitle(updatedLinkTitle); await umbracoUi.content.clickSubmitButton(); await umbracoUi.content.clickSaveButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/CultureAndHostnames.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/CultureAndHostnames.spec.ts index d006dc8e07b4..2f4b301ea9e1 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/CultureAndHostnames.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/CultureAndHostnames.spec.ts @@ -109,6 +109,7 @@ test('can add culture and hostname for multiple languages', async ({umbracoApi, // Act await umbracoUi.content.clickActionsMenuForContent(contentName); await umbracoUi.content.clickCultureAndHostnamesButton(); + await umbracoUi.waitForTimeout(500); await umbracoUi.content.clickAddNewDomainButton(); await umbracoUi.content.enterDomain(domainName, 0); await umbracoUi.content.selectDomainLanguageOption(languageName, 0); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts index cf3b771973b6..12fddb5d0ccb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts @@ -1,4 +1,4 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'TestDataType'; @@ -24,7 +24,7 @@ test('can create a data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); }); @@ -41,6 +41,7 @@ test('can rename a data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.dataType.clickSaveButton(); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); expect(await umbracoApi.dataType.doesNameExist(wrongDataTypeName)).toBeFalsy(); }); @@ -55,7 +56,7 @@ test('can delete a data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.dataType.deleteDataType(dataTypeName); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeFalsy(); }); @@ -75,6 +76,7 @@ test('can change property editor in a data type', {tag: '@smoke'}, async ({umbra await umbracoUi.dataType.clickSaveButton(); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); expect(dataTypeData.editorAlias).toBe(updatedEditorAlias); @@ -110,7 +112,7 @@ test('can change settings', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts index 83606f4da1d6..ce7983581d96 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts @@ -1,4 +1,4 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'TestDataType'; @@ -23,6 +23,7 @@ test('can create a data type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.createFolder(dataTypeFolderName); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(dataTypeFolderName)).toBeTruthy(); }); @@ -41,6 +42,7 @@ test('can rename a data type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickConfirmRenameFolderButton(); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.dataType.doesNameExist(dataTypeFolderName)).toBeTruthy(); expect(await umbracoApi.dataType.doesNameExist(wrongDataTypeFolderName)).toBeFalsy(); }); @@ -55,6 +57,7 @@ test('can delete a data type folder', {tag: '@smoke'}, async ({umbracoApi, umbra await umbracoUi.dataType.deleteDataTypeFolder(dataTypeFolderName); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderName)).toBeFalsy(); }); @@ -75,6 +78,7 @@ test('can create a data type in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.clickSaveButton(); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); const dataTypeChildren = await umbracoApi.dataType.getChildren(dataTypeFolderId); expect(dataTypeChildren[0].name).toBe(dataTypeName); @@ -94,6 +98,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.createFolder(childFolderName); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(childFolderName)).toBeTruthy(); const dataTypeChildren = await umbracoApi.dataType.getChildren(dataTypeFolderId); expect(dataTypeChildren[0].name).toBe(childFolderName); @@ -114,7 +119,7 @@ test('can create a folder in a folder in a folder', async ({umbracoApi, umbracoU await umbracoUi.dataType.createFolder(childOfChildFolderName); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(childOfChildFolderName)).toBeTruthy(); const childrenFolderData = await umbracoApi.dataType.getChildren(childFolderId); expect(childrenFolderData[0].name).toBe(childOfChildFolderName); @@ -135,7 +140,7 @@ test('cannot delete a non-empty data type folder', async ({umbracoApi, umbracoUi await umbracoUi.dataType.deleteDataTypeFolder(dataTypeFolderName); // Assert - await umbracoUi.dataType.isErrorNotificationVisible(); + await umbracoUi.dataType.doesErrorNotificationHaveText(NotificationConstantHelper.error.notEmptyFolder); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); expect(await umbracoApi.dataType.doesNameExist(dataTypeFolderName)).toBeTruthy(); const dataTypeChildren = await umbracoApi.dataType.getChildren(dataTypeFolderId); @@ -161,7 +166,7 @@ test('can move a data type to a data type folder', async ({umbracoApi, umbracoUi await umbracoUi.dataType.moveDataTypeToFolder(dataTypeFolderName); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.moved); const dataTypeInFolder = await umbracoApi.dataType.getChildren(dataTypeFolderId); expect(dataTypeInFolder[0].id).toEqual(dataTypeId); @@ -184,7 +189,7 @@ test('can duplicate a data type to a data type folder', async ({umbracoApi, umbr await umbracoUi.dataType.duplicateDataTypeToFolder(dataTypeFolderName); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.duplicated); const dataTypeInFolder = await umbracoApi.dataType.getChildren(dataTypeFolderId); expect(dataTypeInFolder[0].name).toEqual(dataTypeName + ' (copy)'); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts index ce0e66668c4b..83f4f03cc757 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/ListView.spec.ts @@ -216,8 +216,7 @@ for (const listViewType of listViewTypes) { expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); }); - // Skip this test as there is no setting for content app icon - test.skip('can update content app icon', async ({umbracoApi, umbracoUi}) => { + test('can update workspace view icon', async ({umbracoApi, umbracoUi}) => { // Arrange const iconValue = 'icon-activity'; const expectedDataTypeValues = { @@ -227,8 +226,8 @@ for (const listViewType of listViewTypes) { // Act await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickContentAppIconButton(); - await umbracoUi.dataType.chooseContentAppIconByValue(iconValue); + await umbracoUi.dataType.clickSelectIconButton(); + await umbracoUi.dataType.chooseWorkspaceViewIconByValue(iconValue); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -236,18 +235,17 @@ for (const listViewType of listViewTypes) { expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); }); - // Skip this test as there is no setting for content app icon - test.skip('can update content app name', async ({umbracoApi, umbracoUi}) => { + test('can update workspace view name', async ({umbracoApi, umbracoUi}) => { // Arrange - const contentAppName = 'Test Content App Name'; + const WorkspaceViewName = 'Test Content Name'; const expectedDataTypeValues = { "alias": "tabName", - "value": contentAppName + "value": WorkspaceViewName }; // Act await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.enterContentAppName(contentAppName); + await umbracoUi.dataType.enterWorkspaceViewName(WorkspaceViewName); await umbracoUi.dataType.clickSaveButton(); // Assert @@ -255,8 +253,7 @@ for (const listViewType of listViewTypes) { expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); }); - // Skip this test as there is no setting for content app icon - test.skip('can enable show content app first', async ({umbracoApi, umbracoUi}) => { + test('can enable show content workspace view first', async ({umbracoApi, umbracoUi}) => { // Arrange const expectedDataTypeValues = { "alias": "showContentFirst", @@ -265,7 +262,7 @@ for (const listViewType of listViewTypes) { // Act await umbracoUi.dataType.goToDataType(listViewType); - await umbracoUi.dataType.clickShowContentAppFirstSlider(); + await umbracoUi.dataType.clickShowContentWorkspaceViewFirstSlider(); await umbracoUi.dataType.clickSaveButton(); // Assert diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts index c475d31e01b0..dc2c2cc8f0f4 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dictionaryName = 'TestDictionaryItem'; @@ -24,7 +24,7 @@ test('can create a dictionary item', async ({umbracoApi, umbracoUi}) => { // Assert expect(await umbracoApi.dictionary.doesNameExist(dictionaryName)).toBeTruthy(); - await umbracoUi.dictionary.isSuccessNotificationVisible(); + await umbracoUi.dictionary.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.dictionary.clickLeftArrowButton(); // Verify the dictionary item displays in the tree and in the list await umbracoUi.dictionary.isDictionaryTreeItemVisible(dictionaryName); @@ -42,7 +42,7 @@ test('can delete a dictionary item', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dictionary.deleteDictionary(); // Assert - await umbracoUi.dictionary.isSuccessNotificationVisible(); + await umbracoUi.dictionary.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.dictionary.doesNameExist(dictionaryName)).toBeFalsy(); // Verify the dictionary item does not display in the tree await umbracoUi.dictionary.isDictionaryTreeItemVisible(dictionaryName, false); @@ -66,7 +66,7 @@ test('can create a dictionary item in a dictionary', {tag: '@smoke'}, async ({um await umbracoUi.dictionary.clickSaveButton(); // Assert - await umbracoUi.dictionary.isSuccessNotificationVisible(); + await umbracoUi.dictionary.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const dictionaryChildren = await umbracoApi.dictionary.getChildren(parentDictionaryId); expect(dictionaryChildren[0].name).toEqual(dictionaryName); await umbracoUi.dictionary.clickLeftArrowButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts new file mode 100644 index 000000000000..f28ae14ca006 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts @@ -0,0 +1,160 @@ +import {expect} from '@playwright/test'; +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const dataTypeName = 'List View - Media'; +let dataTypeDefaultData = null; +const firstMediaFileName = 'FirstMediaFile'; +const secondMediaFileName = 'SecondMediaFile'; + +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + dataTypeDefaultData = await umbracoApi.dataType.getByName(dataTypeName); + await umbracoApi.media.ensureNameNotExists(firstMediaFileName); + await umbracoApi.media.ensureNameNotExists(secondMediaFileName); + await umbracoApi.media.createDefaultMediaFile(firstMediaFileName); + await umbracoApi.media.createDefaultMediaFile(secondMediaFileName); + await umbracoUi.goToBackOffice(); +}); + +test.afterEach(async ({umbracoApi}) => { + if (dataTypeDefaultData !== null) { + await umbracoApi.dataType.update(dataTypeDefaultData.id, dataTypeDefaultData); + } + await umbracoApi.media.ensureNameNotExists(firstMediaFileName); + await umbracoApi.media.ensureNameNotExists(secondMediaFileName); + await umbracoApi.media.emptyRecycleBin(); +}); + +test('can change the the default sort order for the list in the media section', async ({umbracoApi, umbracoUi}) => { + // Arrange + const sortOrder = 'creator'; + const expectedMediaValues = await umbracoApi.media.getAllMediaNames(sortOrder); + + // Act + await umbracoApi.dataType.updateListViewMediaDataType('orderBy', sortOrder); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoUi.media.changeToListView(); + await umbracoUi.waitForTimeout(500); + + // Assert + await umbracoUi.media.isMediaListViewVisible(); + await umbracoUi.media.doesMediaListNameValuesMatch(expectedMediaValues); +}); + +test('can change the the order direction for the list in the media section', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedMediaValues = await umbracoApi.media.getAllMediaNames('updateDate', 'Ascending'); + + // Act + await umbracoApi.dataType.updateListViewMediaDataType('orderDirection', 'asc'); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Assert + await umbracoUi.media.isMediaGridViewVisible(); + await umbracoUi.media.doesMediaGridValuesMatch(expectedMediaValues); + await umbracoUi.media.changeToListView(); + await umbracoUi.waitForTimeout(500); + await umbracoUi.media.isMediaListViewVisible(); + await umbracoUi.media.doesMediaListNameValuesMatch(expectedMediaValues); +}); + +test('can add more columns to the list in the media section', async ({umbracoApi, umbracoUi}) => { + // Arrange + const expectedColumns = ['Name', 'Last edited', 'Updated by', 'Size']; + const updatedValue = [ + {"alias": "updateDate", "header": "Last edited", "isSystem": true}, + {"alias": "creator", "header": "Updated by", "isSystem": true}, + {"alias": "umbracoBytes", "header": "Size", "isSystem": 0} + ]; + + // Act + await umbracoApi.dataType.updateListViewMediaDataType('includeProperties', updatedValue); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoUi.media.changeToListView(); + await umbracoUi.waitForTimeout(500); + + // Assert + await umbracoUi.media.isMediaListViewVisible(); + await umbracoUi.media.doesMediaListHeaderValuesMatch(expectedColumns); +}); + +test('can disable one view in the media section', async ({umbracoApi, umbracoUi}) => { + // Arrange + const updatedValue = [ + { + "name": "List", + "collectionView": "Umb.CollectionView.Media.Table", + "icon": "icon-list", + "isSystem": true, + "selected": true + } + ]; + + // Act + await umbracoApi.dataType.updateListViewMediaDataType('layouts', updatedValue); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + + // Assert + await umbracoUi.media.isViewBundleButtonVisible(false); + await umbracoUi.media.isMediaListViewVisible(); + await umbracoUi.media.isMediaGridViewVisible(false); +}); + +test('can allow bulk trash in the media section', async ({umbracoApi, umbracoUi}) => { + // Arrange + const updatedValue = { + "allowBulkPublish": false, + "allowBulkUnpublish": false, + "allowBulkCopy": false, + "allowBulkDelete": true, + "allowBulkMove": false + }; + + // Act + await umbracoApi.dataType.updateListViewMediaDataType('bulkActionPermissions', updatedValue); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoUi.media.selectMediaByName(firstMediaFileName); + await umbracoUi.media.selectMediaByName(secondMediaFileName); + await umbracoUi.media.clickBulkTrashButton(); + await umbracoUi.media.clickConfirmTrashButton(); + + // Assert + await umbracoUi.media.reloadMediaTree(); + expect(await umbracoApi.media.doesNameExist(firstMediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesNameExist(secondMediaFileName)).toBeFalsy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(firstMediaFileName)).toBeTruthy(); + expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(secondMediaFileName)).toBeTruthy(); + await umbracoUi.media.isItemVisibleInRecycleBin(firstMediaFileName); + await umbracoUi.media.isItemVisibleInRecycleBin(secondMediaFileName, true, false); +}); + +test('can allow bulk move in the media section', async ({umbracoApi, umbracoUi}) => { + // Arrange + const mediaFolderName = 'Test Folder Name'; + const updatedValue = { + "allowBulkPublish": false, + "allowBulkUnpublish": false, + "allowBulkCopy": false, + "allowBulkDelete": false, + "allowBulkMove": true + }; + await umbracoApi.media.ensureNameNotExists(mediaFolderName); + const mediaFolderId = await umbracoApi.media.createDefaultMediaFolder(mediaFolderName); + + // Act + await umbracoApi.dataType.updateListViewMediaDataType('bulkActionPermissions', updatedValue); + await umbracoUi.media.goToSection(ConstantHelper.sections.media); + await umbracoUi.media.selectMediaByName(firstMediaFileName); + await umbracoUi.media.selectMediaByName(secondMediaFileName); + await umbracoUi.media.clickBulkMoveToButton(); + await umbracoUi.media.clickCaretButtonForName('Media'); + await umbracoUi.media.clickModalTextByName(mediaFolderName); + await umbracoUi.media.clickChooseModalButton(); + + // Assert + await umbracoUi.media.isSuccessNotificationVisible(); + expect(await umbracoApi.media.doesMediaItemHaveChildName(mediaFolderId, firstMediaFileName)).toBeTruthy(); + expect(await umbracoApi.media.doesMediaItemHaveChildName(mediaFolderId, secondMediaFileName)).toBeTruthy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(mediaFolderName); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index dfc1cb963031..266bf7b2db17 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const mediaFileName = 'TestMediaFile'; @@ -27,7 +27,7 @@ test.skip('can not create a empty media file', async ({umbracoApi, umbracoUi}) = // Assert await umbracoUi.media.isErrorNotificationVisible(); - await umbracoUi.media.isTreeItemVisible(mediaFileName, false); + await umbracoUi.media.isMediaTreeItemVisible(mediaFileName, false); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); }); @@ -45,8 +45,8 @@ test('can rename a media file', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); - await umbracoUi.media.isTreeItemVisible(mediaFileName); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.media.isMediaTreeItemVisible(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); }); @@ -73,8 +73,9 @@ for (const mediaFileType of mediaFileTypes) { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); - await umbracoUi.media.isTreeItemVisible(mediaFileType.fileName); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + await umbracoUi.media.reloadMediaTree(); + await umbracoUi.media.isMediaTreeItemVisible(mediaFileType.fileName); expect(await umbracoApi.media.doesNameExist(mediaFileType.fileName)).toBeTruthy(); // Clean @@ -94,8 +95,8 @@ test('can create a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); - await umbracoUi.media.isTreeItemVisible(folderName); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + await umbracoUi.media.isMediaTreeItemVisible(folderName); expect(await umbracoApi.media.doesNameExist(folderName)).toBeTruthy(); // Clean @@ -134,10 +135,11 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); - await umbracoUi.media.isTreeItemVisible(parentFolderName); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + await umbracoUi.media.isMediaTreeItemVisible(parentFolderName); + await umbracoUi.media.isMediaTreeItemVisible(folderName, false); await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); - await umbracoUi.media.isTreeItemVisible(folderName); + await umbracoUi.media.isMediaTreeItemVisible(folderName, true); // Clean await umbracoApi.media.ensureNameNotExists(parentFolderName); @@ -175,6 +177,7 @@ test('can trash a media item', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickConfirmTrashButton(); // Assert + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeTruthy(); @@ -195,9 +198,10 @@ test('can restore a media item from the recycle bin', async ({umbracoApi, umbrac await umbracoUi.media.restoreMediaItem(mediaFileName); // Assert + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.restored); await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); await umbracoUi.media.reloadMediaTree(); - await umbracoUi.media.isTreeItemVisible(mediaFileName); + await umbracoUi.media.isMediaTreeItemVisible(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); @@ -217,6 +221,7 @@ test('can delete a media item from the recycle bin', async ({umbracoApi, umbraco await umbracoUi.media.deleteMediaItem(mediaFileName); // Assert + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); @@ -236,6 +241,7 @@ test('can empty the recycle bin', async ({umbracoApi, umbracoUi}) => { // Assert await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.emptiedRecycleBin); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/MemberGroups.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/MemberGroups.spec.ts index 0ca851097b30..afbe007ac6d7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/MemberGroups.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/MemberGroups.spec.ts @@ -1,4 +1,4 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const memberGroupName = 'Test Member Group'; @@ -20,7 +20,7 @@ test('can create a member group', {tag: '@smoke'}, async ({umbracoApi, umbracoUi await umbracoUi.memberGroup.clickSaveButton(); // Assert - await umbracoUi.memberGroup.isSuccessNotificationVisible(); + await umbracoUi.memberGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.memberGroup.clickLeftArrowButton(); await umbracoUi.memberGroup.isMemberGroupNameVisible(memberGroupName); expect(await umbracoApi.memberGroup.doesNameExist(memberGroupName)).toBeTruthy(); @@ -32,7 +32,7 @@ test('cannot create member group with empty name', async ({umbracoApi, umbracoUi await umbracoUi.memberGroup.clickSaveButton(); // Assert - await umbracoUi.memberGroup.isErrorNotificationVisible(); + await umbracoUi.memberGroup.doesErrorNotificationHaveText(NotificationConstantHelper.error.emptyName); expect(await umbracoApi.memberGroup.doesNameExist(memberGroupName)).toBeFalsy(); }); @@ -47,7 +47,7 @@ test('cannot create member group with duplicate name', async ({umbracoApi, umbra await umbracoUi.memberGroup.clickSaveButton(); // Assert - await umbracoUi.memberGroup.isErrorNotificationVisible(); + await umbracoUi.memberGroup.doesErrorNotificationHaveText(NotificationConstantHelper.error.duplicateName); }); test('can delete a member group', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts index 955c667bfb14..61f30db52145 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Members/Members.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; let memberId = ''; @@ -38,7 +38,7 @@ test('can create a member', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.member.doesNameExist(memberName)).toBeTruthy(); }); @@ -54,7 +54,7 @@ test('can edit comments', async ({umbracoApi, umbracoUi}) => { await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const memberData = await umbracoApi.member.get(memberId); expect(memberData.values[0].value).toBe(comment); }); @@ -72,7 +72,7 @@ test('can edit username', async ({umbracoApi, umbracoUi}) => { await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const memberData = await umbracoApi.member.get(memberId); expect(memberData.username).toBe(updatedUsername); }); @@ -90,7 +90,7 @@ test('can edit email', async ({umbracoApi, umbracoUi}) => { await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const memberData = await umbracoApi.member.get(memberId); expect(memberData.email).toBe(updatedEmail); }); @@ -110,7 +110,7 @@ test('can edit password', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); }); test('can add member group', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { @@ -128,7 +128,7 @@ test('can add member group', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const memberData = await umbracoApi.member.get(memberId); expect(memberData.groups[0]).toBe(memberGroupId); @@ -152,7 +152,7 @@ test('can remove member group', async ({umbracoApi, umbracoUi}) => { await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const memberData = await umbracoApi.member.get(memberId); expect(memberData.groups.length).toBe(0); @@ -196,7 +196,7 @@ test('can enable approved', async ({umbracoApi, umbracoUi}) => { await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const memberData = await umbracoApi.member.get(memberId); expect(memberData.isApproved).toBe(true); }); @@ -206,7 +206,7 @@ test('can delete member', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { memberTypeId = await umbracoApi.memberType.createDefaultMemberType(memberTypeName); memberId = await umbracoApi.member.createDefaultMember(memberName, memberTypeId, email, username, password); await umbracoUi.member.goToMembers(); - + // Act await umbracoUi.member.clickMemberLinkByName(memberName); await umbracoUi.memberGroup.clickActionsButton(); @@ -214,7 +214,7 @@ test('can delete member', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.memberGroup.clickConfirmToDeleteButton(); // Assert - await umbracoUi.member.isSuccessNotificationVisible(); + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.member.doesNameExist(memberName)).toBeFalsy(); }); @@ -235,7 +235,7 @@ test('cannot create member with invalid email', async ({umbracoApi, umbracoUi}) await umbracoUi.member.clickSaveButton(); // Assert - await umbracoUi.member.isErrorNotificationVisible(); + await umbracoUi.member.doesErrorNotificationHaveText(NotificationConstantHelper.error.invalidEmail); expect(await umbracoApi.member.doesNameExist(memberName)).toBeFalsy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts index 0b72f77f0ccd..a0073862aeef 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts @@ -1,373 +1,347 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -import {expect} from '@playwright/test'; -import * as fs from 'fs'; - -const packageName = 'TestPackage'; - -test.beforeEach(async ({umbracoApi, umbracoUi}) => { - await umbracoApi.package.ensureNameNotExists(packageName); - await umbracoUi.goToBackOffice(); - await umbracoUi.package.goToSection(ConstantHelper.sections.packages); - await umbracoUi.package.clickCreatedTab(); -}); - -test.afterEach(async ({umbracoApi}) => { - await umbracoApi.package.ensureNameNotExists(packageName); -}); - -test.skip('can create a empty package', {tag: '@smoke'}, async ({umbracoUi}) => { - // Act - await umbracoUi.package.clickCreatePackageButton(); - await umbracoUi.package.enterPackageName(packageName); - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.isPackageNameVisible(packageName); -}); - -test.skip('can update package name', async ({umbracoApi, umbracoUi}) => { - // Arrange - const wrongPackageName = 'WrongPackageName'; - await umbracoApi.package.ensureNameNotExists(wrongPackageName); - await umbracoApi.package.createEmptyPackage(wrongPackageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(wrongPackageName); - await umbracoUi.package.enterPackageName(packageName); - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.isPackageNameVisible(packageName); - expect(umbracoApi.package.doesNameExist(packageName)).toBeTruthy(); -}); - -test.skip('can delete a package', async ({umbracoApi, umbracoUi}) => { - // Arrange - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickDeleteButtonForPackageName(packageName); - await umbracoUi.package.clickDeleteExactLabel(); - - // Assert - await umbracoUi.package.isPackageNameVisible(packageName, false); - expect(await umbracoApi.package.doesNameExist(packageName)).toBeFalsy(); -}); - -// TODO: Update the locators for the choose button. If it is updated or not -// TODO: Remove .skip when the test is able to run. Currently it is not possible to add content to a package -test.skip('can create a package with content', async ({page, umbracoApi, umbracoUi}) => { - // Arrange - const documentTypeName = 'TestDocumentType'; - const documentName = 'TestDocument'; - await umbracoApi.documentType.ensureNameNotExists(documentTypeName); - await umbracoApi.package.createEmptyPackage(packageName); - const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); - const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - // The frontend has updated the button name to "Choose" of "Add". But they are a bit unsure if they want to change it to select instead. - // So for the moment I have used the page instead of our UiHelper. Because it is easier to change the locator. - // await umbracoUi.package.clickAddContentToPackageButton(); - await page.locator('[label="Content"] >> [label="Choose"]').click(); - await umbracoUi.package.clickLabelWithName(documentName); - await umbracoUi.package.clickChooseBtn(); - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.contentNodeId == documentId).toBeTruthy(); - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(documentName + ' ' + documentId)).toBeTruthy(); - - // Clean - await umbracoApi.documentType.ensureNameNotExists(documentTypeName); -}); - -// Currently unable to run this test. Because you are not able to save a mediaId -test.skip('can create a package with media', async ({umbracoApi, umbracoUi}) => { - // Arrange - const mediaTypeName = 'TestMediaType'; - const mediaName = 'TestMedia'; - await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); - await umbracoApi.package.createEmptyPackage(packageName); - const mediaTypeId = await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName); - const mediaId = await umbracoApi.media.createDefaultMedia(mediaName, mediaTypeId); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddMediaToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(mediaName); - await umbracoUi.package.clickSubmitButton(); - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(mediaTypeName + ' ' + mediaId)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.mediaIds[0] == mediaId).toBeTruthy(); - - // Clean - await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); -}); - -test.skip('can create a package with document types', async ({umbracoApi, umbracoUi}) => { - // Arrange - const documentTypeName = 'TestDocumentType'; - await umbracoApi.documentType.ensureNameNotExists(documentTypeName); - await umbracoApi.package.createEmptyPackage(packageName); - const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddDocumentTypeToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(documentTypeName); - await umbracoUi.package.clickSubmitButton(); - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(documentTypeName + ' ' + documentTypeId)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.documentTypes[0] == documentTypeId).toBeTruthy(); - - // Clean - await umbracoApi.documentType.ensureNameNotExists(documentTypeName); -}); - -// TODO: Remove .skip when the test is able to run. Currently waiting for button -test.skip('can create a package with media types', async ({umbracoApi, umbracoUi}) => { - // Arrange - const mediaTypeName = 'TestMediaType'; - await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); - await umbracoApi.package.createEmptyPackage(packageName); - const mediaTypeId = await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddMediaTypeToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(mediaTypeName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(mediaTypeName + ' ' + mediaTypeId)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.mediaTypes[0] == mediaTypeId).toBeTruthy(); - - // Clean - await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); -}); - -// TODO: Remove .skip when the test is able to run. After adding a language to a package and saving. The language is not saved or anything. -test.skip('can create a package with languages', async ({umbracoApi, umbracoUi}) => { - // Arrange - const languageId = await umbracoApi.language.createDefaultDanishLanguage(); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - const languageData = await umbracoApi.language.get(languageId); - const languageName = languageData.name; - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddLanguageToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(languageName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(languageName + ' ' + languageId)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.languages[0] == languageId).toBeTruthy(); - - // Clean - await umbracoApi.language.ensureNameNotExists(languageName); -}); - -// TODO: Remove .skip when the test is able to run. Currently waiting for button -test.skip('can create a package with dictionary', async ({umbracoApi, umbracoUi}) => { - // Arrange - const dictionaryName = 'TestDictionary'; - await umbracoApi.dictionary.createDefaultDictionary(dictionaryName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddDictionaryToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(dictionaryName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(dictionaryName)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.dictionaryItems[0] == dictionaryName).toBeTruthy(); - - // Clean - await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); -}); - -// TODO: Remove .skip when the test is able to run. After adding a dataType to a package and saving. The datatype is not saved or anything. -test.skip('can create a package with data types', async ({umbracoApi, umbracoUi}) => { - // Arrange - const dataTypeName = 'TestDataType'; - const dataTypeId = await umbracoApi.dataType.createDateTypeDataType(dataTypeName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddDataTypesToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(dataTypeName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(dataTypeName)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.dataTypes[0] == dataTypeId).toBeTruthy(); - - // Clean - await umbracoApi.dictionary.ensureNameNotExists(dataTypeName); -}); - -// TODO: Remove .skip when the test is able to run. Currently waiting for button -test.skip('can create a package with templates', async ({umbracoApi, umbracoUi}) => { - // Arrange - const templateName = 'TestTemplate'; - const templateId = await umbracoApi.template.createDefaultTemplate(templateName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddTemplatesToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(templateName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(templateName)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.templates[0] == templateId).toBeTruthy(); - - // Clean - await umbracoApi.template.ensureNameNotExists(templateName); -}); - -// TODO: Remove .skip when the test is able to run. Currently waiting for button -test.skip('can create a package with stylesheets', async ({umbracoApi, umbracoUi}) => { - // Arrange - const stylesheetName = 'TestStylesheet'; - const stylesheetId = await umbracoApi.stylesheet.createDefaultStylesheet(stylesheetName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddStylesheetToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(stylesheetName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(stylesheetName)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.stylesheets[0] == stylesheetId).toBeTruthy(); - - // Clean - await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); -}); - -// TODO: Remove .skip when the test is able to run. Currently waiting for button -test.skip('can create a package with scripts', async ({umbracoApi, umbracoUi}) => { - // Arrange - const scriptName = 'TestScripts'; - const scriptId = await umbracoApi.script.createDefaultScript(scriptName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddScriptToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(scriptName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(scriptName)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.scripts[0] == scriptId).toBeTruthy(); - - // Clean - await umbracoApi.script.ensureNameNotExists(scriptName); -}); - -// TODO: Remove .skip when the test is able to run. Currently waiting for button -test.skip('can create a package with partial views', async ({umbracoApi, umbracoUi}) => { - // Arrange - const partialViewName = 'TestPartialView'; - const partialViewId = await umbracoApi.partialView.createDefaultPartialView(partialViewName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - await umbracoUi.package.clickAddPartialViewToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(partialViewName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); - - // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(partialViewName)).toBeTruthy(); - const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.partialViews[0] == partialViewId).toBeTruthy(); - - // Clean - await umbracoApi.package.ensureNameNotExists(packageName); -}); - -// Currently you are not able to download a package -//TODO: Remove skip when the frontend is ready -test.skip('can download a package', async ({umbracoApi, umbracoUi}) => { - // Arrange - const packageId = await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); - - // Act - await umbracoUi.package.clickExistingPackageName(packageName); - const packageData = await umbracoUi.package.downloadPackage(packageId); - // Reads the packageFixture we have in the fixture library - const path = require('path'); - const filePath = path.resolve('./fixtures/packageLibrary/package.xml'); - const packageFixture = fs.readFileSync(filePath); - - // Assert - expect(packageData).toMatch(packageFixture.toString().trim()); -}); +// import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +// import {expect} from '@playwright/test'; +// import * as fs from 'fs'; +// +// const packageName = 'TestPackage'; +// // UNCOMMENT WHEN FIXED +// test.beforeEach(async ({umbracoApi, umbracoUi}) => { +// await umbracoApi.package.ensureNameNotExists(packageName); +// await umbracoUi.goToBackOffice(); +// await umbracoUi.package.goToSection(ConstantHelper.sections.packages); +// await umbracoUi.package.clickCreatedTab(); +// }); +// +// test.afterEach(async ({umbracoApi}) => { +// await umbracoApi.package.ensureNameNotExists(packageName); +// }); +// +// test('can create a empty package', {tag: '@smoke'}, async ({ umbracoUi}) => { +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// await umbracoUi.package.clickCreatedTab(); +// await umbracoUi.package.isPackageNameVisible(packageName); +// }); +// +// test('can update package name', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const wrongPackageName = 'WrongPackageName'; +// await umbracoApi.package.ensureNameNotExists(wrongPackageName); +// await umbracoApi.package.createEmptyPackage(wrongPackageName); +// await umbracoUi.reloadPage(); +// await umbracoUi.package.goToSection(ConstantHelper.sections.packages); +// await umbracoUi.package.clickCreatedTab(); +// +// // Act +// await umbracoUi.package.clickExistingPackageName(wrongPackageName); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickUpdateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// await umbracoUi.package.clickCreatedTab(); +// await umbracoUi.package.isPackageNameVisible(packageName); +// expect(umbracoApi.package.doesNameExist(packageName)).toBeTruthy(); +// }); +// +// test('can delete a package', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// await umbracoApi.package.createEmptyPackage(packageName); +// await umbracoUi.reloadPage(); +// await umbracoUi.package.clickCreatedTab(); +// +// // Act +// await umbracoUi.package.clickDeleteButtonForPackageName(packageName); +// await umbracoUi.package.clickConfirmToDeleteButton(); +// +// // Assert +// await umbracoUi.package.clickCreatedTab(); +// await umbracoUi.package.isPackageNameVisible(packageName, false); +// expect(await umbracoApi.package.doesNameExist(packageName)).toBeFalsy(); +// }); +// +// test('can create a package with content', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const documentTypeName = 'TestDocumentType'; +// const documentName = 'TestDocument'; +// await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +// const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); +// const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddContentToPackageButton(); +// await umbracoUi.package.clickLabelWithName(documentName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.contentNodeId == documentId).toBeTruthy(); +// expect(umbracoUi.package.isButtonWithNameVisible(documentName)).toBeTruthy(); +// +// // Clean +// await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +// }); +// +// test('can create a package with media', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const mediaName = 'TestMedia'; +// await umbracoApi.media.ensureNameNotExists(mediaName); +// const mediaId = await umbracoApi.media.createDefaultMediaFile(mediaName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddMediaToPackageButton(); +// await umbracoUi.media.selectMediaByName(mediaName); +// await umbracoUi.package.clickSubmitButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isTextWithExactNameVisible(mediaName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.mediaIds[0] == mediaId).toBeTruthy(); +// +// // Clean +// await umbracoApi.media.ensureNameNotExists(mediaName); +// }); +// +// test('can create a package with document types', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const documentTypeName = 'TestDocumentType'; +// await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +// const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddDocumentTypeToPackageButton(); +// await umbracoUi.package.clickLabelWithName(documentTypeName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(documentTypeName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.documentTypes[0] == documentTypeId).toBeTruthy(); +// +// // Clean +// await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +// }); +// +// test('can create a package with media types', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const mediaTypeName = 'TestMediaType'; +// await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); +// const mediaTypeId = await umbracoApi.mediaType.createDefaultMediaType(mediaTypeName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddMediaTypeToPackageButton(); +// await umbracoUi.package.clickButtonWithName(mediaTypeName, true); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(mediaTypeName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.mediaTypes[0] == mediaTypeId).toBeTruthy(); +// +// // Clean +// await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); +// }); +// +// test('can create a package with languages', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// await umbracoApi.language.ensureNameNotExists('Danish'); +// const languageId = await umbracoApi.language.createDanishLanguage(); +// const languageData = await umbracoApi.language.get(languageId); +// const languageName = languageData.name; +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddLanguageToPackageButton(); +// await umbracoUi.package.clickButtonWithName(languageName); +// await umbracoUi.package.clickSubmitButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(languageName + ' ' + languageId)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.languages[0] == languageId).toBeTruthy(); +// +// // Clean +// await umbracoApi.language.ensureNameNotExists(languageName); +// }); +// +// test('can create a package with dictionary', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const dictionaryName = 'TestDictionary'; +// const dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(dictionaryName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddDictionaryToPackageButton(); +// await umbracoUi.package.clickButtonWithName(dictionaryName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(dictionaryName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.dictionaryItems[0] == dictionaryId).toBeTruthy(); +// +// // Clean +// await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); +// }); +// +// test('can create a package with data types', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const dataTypeName = 'TestDataType'; +// await umbracoApi.dataType.ensureNameNotExists(dataTypeName); +// const dataTypeId = await umbracoApi.dataType.createDateTypeDataType(dataTypeName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddDataTypesToPackageButton(); +// await umbracoUi.package.clickLabelWithName(dataTypeName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(dataTypeName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.dataTypes[0] == dataTypeId).toBeTruthy(); +// +// // Clean +// await umbracoApi.dataType.ensureNameNotExists(dataTypeName); +// }); +// +// test('can create a package with templates', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const templateName = 'TestTemplate'; +// await umbracoApi.template.ensureNameNotExists(templateName); +// const templateId = await umbracoApi.template.createDefaultTemplate(templateName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddTemplatesToPackageButton(); +// await umbracoUi.package.clickLabelWithName(templateName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(templateName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.templates[0] == templateId).toBeTruthy(); +// +// // Clean +// await umbracoApi.template.ensureNameNotExists(templateName); +// }); +// +// test('can create a package with stylesheets', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const stylesheetName = 'TestStylesheet.css'; +// await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); +// const stylesheetId = await umbracoApi.stylesheet.createDefaultStylesheet(stylesheetName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddStylesheetToPackageButton(); +// await umbracoUi.package.clickLabelWithName(stylesheetName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(stylesheetName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.stylesheets[0] == stylesheetId).toBeTruthy(); +// +// // Clean +// await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); +// }); +// +// test('can create a package with scripts', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const scriptName = 'TestScripts.js'; +// await umbracoApi.script.ensureNameNotExists(scriptName); +// const scriptId = await umbracoApi.script.createDefaultScript(scriptName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddScriptToPackageButton(); +// await umbracoUi.package.clickLabelWithName(scriptName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(scriptName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.scripts[0] == scriptId).toBeTruthy(); +// +// // Clean +// await umbracoApi.script.ensureNameNotExists(scriptName); +// }); +// +// test('can create a package with partial views', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const partialViewName = 'TestPartialView.cshtml'; +// const partialViewId = await umbracoApi.partialView.createDefaultPartialView(partialViewName); +// +// // Act +// await umbracoUi.package.clickCreatePackageButton(); +// await umbracoUi.package.enterPackageName(packageName); +// await umbracoUi.package.clickAddPartialViewToPackageButton(); +// await umbracoUi.package.clickLabelWithName(partialViewName); +// await umbracoUi.package.clickChooseContainerButton(); +// await umbracoUi.package.clickCreateButton(); +// +// // Assert +// await umbracoUi.package.isSuccessNotificationVisible(); +// expect(umbracoUi.package.isButtonWithNameVisible(partialViewName)).toBeTruthy(); +// const packageData = await umbracoApi.package.getByName(packageName); +// expect(packageData.partialViews[0] == partialViewId).toBeTruthy(); +// +// // Clean +// await umbracoApi.partialView.ensureNameNotExists(partialViewName); +// }); +// +// test('can download a package', async ({umbracoApi, umbracoUi}) => { +// // Arrange +// const packageId = await umbracoApi.package.createEmptyPackage(packageName); +// await umbracoUi.reloadPage(); +// +// // Act +// await umbracoUi.package.clickExistingPackageName(packageName); +// const packageData = await umbracoUi.package.downloadPackage(packageId); +// // Reads the packageFixture we have in the fixture library +// const path = require('path'); +// const filePath = path.resolve('./fixtures/packageLibrary/package.xml'); +// const packageFixture = fs.readFileSync(filePath); +// +// // Assert +// expect(packageData).toMatch(packageFixture.toString().trim()); +// }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts index 603b52d1e71b..fbd11c8cfd24 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts @@ -1,8 +1,6 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -// We can't install any packages so we do not have any installed. -//TODO: Remove skip when the frontend is ready -test.skip('can see no package have been installed', async ({page, umbracoUi}) => { +test.skip('can see the umbraco package is installed', async ({umbracoUi}) => { // Arrange await umbracoUi.goToBackOffice(); await umbracoUi.package.goToSection(ConstantHelper.sections.packages); @@ -11,5 +9,5 @@ test.skip('can see no package have been installed', async ({page, umbracoUi}) => await umbracoUi.package.clickInstalledTab(); // Assert - await umbracoUi.package.isTextNoPackagesHaveBeenInstalledVisible(); + await umbracoUi.package.isUmbracoBackofficePackageVisible(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts index 4e7a0b3057a0..a1b4d192ad5a 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts @@ -1,7 +1,8 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +// TODO: There is currently an issue with playwright and the Iframe in the marketplace, look into this. // The MarketPlace is a iFrame we are using from the DXP team, so it is not something we should test. This test is just checking if we have the IFrame -test('can see the marketplace', async ({umbracoUi}) => { +test.skip('can see the marketplace', async ({umbracoUi}) => { // Arrange await umbracoUi.goToBackOffice(); await umbracoUi.package.goToSection(ConstantHelper.sections.packages); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts new file mode 100644 index 000000000000..5daad8769b01 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts @@ -0,0 +1,48 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Custom Approved Color'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Approved Color'; +const colorValue = {label: "Test Label", value: "038c33"}; +let dataTypeId = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeId = await umbracoApi.dataType.createApprovedColorDataTypeWithOneItem(customDataTypeName, colorValue.label, colorValue.value); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test('can render content with an approved color with label', async ({umbracoApi, umbracoUi}) => { + // Arrange + const templateId = await umbracoApi.template.createTemplateWithDisplayingApprovedColorValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, colorValue, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(colorValue.label); +}); + +test('can render content with an approved color without label', async ({umbracoApi, umbracoUi}) => { + // Arrange + const templateId = await umbracoApi.template.createTemplateWithDisplayingApprovedColorValue(templateName, AliasHelper.toAlias(propertyName), false); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, colorValue, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(colorValue.value); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts new file mode 100644 index 000000000000..83fe60e58b59 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts @@ -0,0 +1,40 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Custom Checkbox List'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Checkbox List'; + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +const checkboxList = [ + {type: 'an empty list of checkboxes', value: []}, + {type: 'one checkbox', value: ['Test checkbox']}, + {type: 'multiple checkboxes', value: ['Test checkbox 1', 'Test checkbox 2', 'Test checkbox 3']}, +]; + +for (const checkbox of checkboxList) { + test(`can render content with ${checkbox.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const checkboxValue = checkbox.value; + const dataTypeId = await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, checkboxValue); + const templateId = await umbracoApi.template.createTemplateWithDisplayingMulitpleStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, checkboxValue, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + checkboxValue.forEach(async value => { + await umbracoUi.contentRender.doesContentRenderValueContainText(value); + }); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithContentPicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithContentPicker.spec.ts new file mode 100644 index 000000000000..a5246a04bd19 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithContentPicker.spec.ts @@ -0,0 +1,41 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Content Picker'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Content Picker'; +const contentPickerDocumentTypeName = 'DocumentTypeForContentPicker'; +const contentPickerName = 'TestContentPickerName'; +let dataTypeData = null; +let contentPickerDocumentTypeId = ''; +let contentPickerId = ''; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + contentPickerDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(contentPickerDocumentTypeName); + contentPickerId = await umbracoApi.document.createDefaultDocument(contentPickerName, contentPickerDocumentTypeId); + await umbracoApi.document.publish(contentPickerId); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentPickerName); + await umbracoApi.documentType.ensureNameNotExists(contentPickerDocumentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +test('can render content with content picker value', async ({umbracoApi, umbracoUi}) => { + // Arrange + const templateId = await umbracoApi.template.createTemplateWithDisplayingContentPickerValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, contentPickerId, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(contentPickerName); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDatePicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDatePicker.spec.ts new file mode 100644 index 000000000000..212ff8c35e0d --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDatePicker.spec.ts @@ -0,0 +1,35 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Date Picker'; + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +const dateTimes = [ + {type: 'with AM time', value: '2024-10-29 09:09:09', expectedValue: '10/29/2024 9:09:09 AM', dataTypeName: 'Date Picker with time'}, + {type: 'with PM time', value: '2024-10-29 21:09:09', expectedValue: '10/29/2024 9:09:09 PM', dataTypeName: 'Date Picker with time'}, + // TODO: Uncomment this when the front-end is ready. Currently the time still be rendered. + //{type: 'without time', value: '2024-10-29 00:00:00', expectedValue: '10/29/2024', dataTypeName: 'Date Picker'} +]; + +for (const dateTime of dateTimes) { + test(`can render content with a date ${dateTime.type}`, async ({umbracoApi, umbracoUi}) => { + const dataTypeData = await umbracoApi.dataType.getByName(dateTime.dataTypeName); + const templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, dateTime.value, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(dateTime.expectedValue, true); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDropdown.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDropdown.spec.ts new file mode 100644 index 000000000000..8cb3992554de --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDropdown.spec.ts @@ -0,0 +1,44 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Custom Dropdown'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Dropdown'; + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +const dropdownValues = [ + {type: 'an empty dropdown list', value: [], isMultiple: false}, + {type: 'a single dropdown value', value: ['Test checkbox'], isMultiple: false}, + {type: 'multiple dropdown values', value: ['Test option 1', 'Test option 2'], isMultiple: true} +]; + +for (const dropdown of dropdownValues) { + test(`can render content with ${dropdown.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const dataTypeId = await umbracoApi.dataType.createDropdownDataType(customDataTypeName, dropdown.isMultiple, dropdown.value); + let templateId = ''; + if (dropdown.isMultiple) { + templateId = await umbracoApi.template.createTemplateWithDisplayingMulitpleStringValue(templateName, AliasHelper.toAlias(propertyName)); + } else { + templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + } + await umbracoApi.document.createPublishedDocumentWithValue(contentName, dropdown.value, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + dropdown.value.forEach(async value => { + await umbracoUi.contentRender.doesContentRenderValueContainText(value); + }); + }); +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithImageCropper.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithImageCropper.spec.ts new file mode 100644 index 000000000000..de6c40da56f7 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithImageCropper.spec.ts @@ -0,0 +1,36 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Custom Image Cropper'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Image Cropper'; +const cropLabel = 'Test Crop'; +const cropValue = {label: cropLabel, alias: AliasHelper.toAlias(cropLabel), width: 500, height: 700}; +let dataTypeId = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeId = await umbracoApi.dataType.createImageCropperDataTypeWithOneCrop(customDataTypeName, cropValue.label, cropValue.width, cropValue.height); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +test('can render content with an image cropper', async ({umbracoApi, umbracoUi}) => { + // Arrange + const templateId = await umbracoApi.template.createTemplateWithDisplayingImageCropperValue(templateName, AliasHelper.toAlias(propertyName), AliasHelper.toAlias(cropValue.label)); + await umbracoApi.document.createPublishedDocumentWithImageCropper(contentName, cropValue, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + const imageSrc = contentData.values[0].value.src; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueHaveImage(imageSrc, cropValue.width, cropValue.height); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts new file mode 100644 index 000000000000..48985217b4da --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts @@ -0,0 +1,40 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Numeric'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Numeric'; +let dataTypeData = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +const numerics = [ + {type: 'a positive integer', value: '1234567890'}, + {type: 'a negative integer', value: '-1234567890'}, +]; + +for (const numeric of numerics) { + test(`can render content with ${numeric.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const numericValue = numeric.value; + const templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, numericValue, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(numericValue); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithRadiobox.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithRadiobox.spec.ts new file mode 100644 index 000000000000..540f0c2b0561 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithRadiobox.spec.ts @@ -0,0 +1,36 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Custom Radiobox'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Radiobox'; + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +const radioboxValues = [ + {type: 'an empty radiobox', value: ''}, + {type: 'a radiobox value', value: 'Test radiobox option'} +]; + +for (const radiobox of radioboxValues) { + test(`can render content with ${radiobox.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const dataTypeId = await umbracoApi.dataType.createRadioboxDataType(customDataTypeName, [radiobox.value]); + const templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, radiobox.value, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(radiobox.value); + }); +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts new file mode 100644 index 000000000000..6b7e8423b683 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts @@ -0,0 +1,43 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Tags'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Tags'; +let dataTypeData = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +const tags = [ + {type: 'an empty tag', value: []}, + {type: 'a non-empty tag', value: ['test tag']}, + {type: 'multiple tags', value: ['test tag 1', 'test tag 2']}, +]; + +for (const tag of tags) { + test(`can render content with ${tag.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const tagValue = tag.value; + const templateId = await umbracoApi.template.createTemplateWithDisplayingMulitpleStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, tagValue, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + tagValue.forEach(async value => { + await umbracoUi.contentRender.doesContentRenderValueContainText(value); + }); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts new file mode 100644 index 000000000000..251bf997e110 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts @@ -0,0 +1,44 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Textarea'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Textarea'; +let dataTypeData = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +const textareas = [ + {type: 'an empty textarea', value: ''}, + {type: 'a non-empty textarea', value: 'Welcome to Umbraco site'}, + {type: 'a textarea that contains special characters', value: '@#^&*()_+[]{};:"<>,./?'}, + {type: 'a textarea that contains multiple lines', value: 'First line\n Second line\n Third line'}, + {type: 'a textarea that contains an SQL injection', value: "' OR '1'='1'; --"}, + {type: 'a textarea that contains cross-site scripting', value: ""} +]; + +for (const textarea of textareas) { + test(`can render content with ${textarea.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const textareaValue = textarea.value; + const templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, textareaValue, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(textareaValue); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts new file mode 100644 index 000000000000..36944e5f457b --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts @@ -0,0 +1,44 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Textstring'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Textstring'; +let dataTypeData = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +const textstrings = [ + {type: 'an empty textstring', value: ''}, + {type: 'a non-empty textstring', value: 'Welcome to Umbraco site'}, + {type: 'a textstring contains special characters', value: '@#^&*()_+[]{};:"<>,./?'}, + {type: 'a numeric textstring', value: '0123456789'}, + {type: 'a textstring contains an SQL injection', value: "' OR '1'='1'; --"}, + {type: 'a textstring contains a cross-site scripting', value: ""} +]; + +for (const textstring of textstrings) { + test(`can render content with ${textstring.type}`, {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { + // Arrange + const textstringValue = textstring.value; + const templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, textstringValue, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(textstringValue); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts new file mode 100644 index 000000000000..9ef50db657ea --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts @@ -0,0 +1,39 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'True/false'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test TrueFalse'; +let dataTypeData = null; + +test.beforeEach(async ({umbracoApi}) => { + dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +const trueFalseValues = [ + {type: 'true value ', value: true, expectedValue: 'True'}, + {type: 'false value', value: false, expectedValue: 'False'}, +]; + +for (const trueFalse of trueFalseValues) { + test(`can render content with ${trueFalse.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + await umbracoApi.document.createPublishedDocumentWithValue(contentName, trueFalse.value, dataTypeData.id, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueContainText(trueFalse.expectedValue); + }); +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/ExamineManagement.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/ExamineManagement.spec.ts index b8d057724350..b5b61a5faf80 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/ExamineManagement.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/ExamineManagement.spec.ts @@ -36,7 +36,6 @@ test('can view the details of an index', async ({umbracoApi, umbracoUi}) => { await umbracoUi.examineManagement.doesIndexPropertyHaveValue('CommitCount', indexData.providerProperties.CommitCount.toString()); await umbracoUi.examineManagement.doesIndexPropertyHaveValue('DefaultAnalyzer', indexData.providerProperties.DefaultAnalyzer); await umbracoUi.examineManagement.doesIndexPropertyHaveValue('LuceneDirectory', indexData.providerProperties.LuceneDirectory); - //await umbracoUi.examineManagement.doesIndexPropertyHaveValue('LuceneIndexFolder', indexData.providerProperties.LuceneIndexFolder); await umbracoUi.examineManagement.doesIndexPropertyHaveValue('DirectoryFactory', indexData.providerProperties.DirectoryFactory); await umbracoUi.examineManagement.doesIndexPropertyHaveValue('EnableDefaultEventHandler', indexData.providerProperties.EnableDefaultEventHandler.toString()); await umbracoUi.examineManagement.doesIndexPropertyHaveValue('PublishedValuesOnly', indexData.providerProperties.PublishedValuesOnly.toString()); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/PublishedStatus.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/PublishedStatus.spec.ts index f9f8ca9a6854..8840eeff543d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/PublishedStatus.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Dashboard/PublishedStatus.spec.ts @@ -6,19 +6,6 @@ test.beforeEach(async ({umbracoUi}) => { await umbracoUi.publishedStatus.clickPublishedStatusTab(); }); -test('can refresh published cache status', async ({umbracoApi, umbracoUi}) => { - // Arrange - const expectedStatus = await umbracoApi.publishedCache.getStatus(); - - // Act - await umbracoUi.publishedStatus.clickRefreshStatusButton(); - // TODO: create a content item, and check if the ContentStore contains the content or not. - - // Assert - await umbracoUi.publishedStatus.isSuccessButtonWithTextVisible('Refresh Status'); - await umbracoUi.publishedStatus.isPublishedCacheStatusVisible(expectedStatus); -}); - test('can reload the memory cache', async ({umbracoUi}) => { // Act await umbracoUi.publishedStatus.clickReloadMemoryCacheButton(); @@ -36,12 +23,3 @@ test('can rebuild the database cache', async ({umbracoUi}) => { // Assert await umbracoUi.publishedStatus.isSuccessButtonWithTextVisible('Rebuild Database Cache'); }); - -test('can snapshot internal cache', async ({umbracoUi}) => { - // Act - await umbracoUi.publishedStatus.clickSnapshotInternalCacheButton(); - await umbracoUi.publishedStatus.clickContinueButton(); - - // Assert - await umbracoUi.publishedStatus.isSuccessButtonWithTextVisible('Snapshot Internal Cache'); -}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts index db71845ef117..67e5844fee18 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const documentBlueprintName = 'TestDocumentBlueprints'; @@ -29,7 +29,7 @@ test('can create a document blueprint from the settings menu', {tag: '@smoke'}, await umbracoUi.documentBlueprint.clickSaveButton(); // Assert - await umbracoUi.documentBlueprint.isSuccessNotificationVisible(); + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeTruthy(); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, true); }); @@ -48,6 +48,7 @@ test('can rename a document blueprint', async ({umbracoApi, umbracoUi}) => { await umbracoUi.documentBlueprint.clickSaveButton(); // Assert + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeTruthy(); expect(await umbracoApi.documentBlueprint.doesNameExist(wrongDocumentBlueprintName)).toBeFalsy(); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, true, false); @@ -67,7 +68,7 @@ test('can delete a document blueprint', async ({umbracoApi, umbracoUi}) => { await umbracoUi.documentBlueprint.clickConfirmToDeleteButton(); // Assert - await umbracoUi.documentBlueprint.isSuccessNotificationVisible(); + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeFalsy(); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, false, false); }); @@ -85,7 +86,7 @@ test('can create a document blueprint from the content menu', async ({umbracoApi await umbracoUi.content.clickSaveModalButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeTruthy(); await umbracoUi.documentBlueprint.goToSettingsTreeItem('Document Blueprints'); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, true); 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 index 0794326445ed..045882fabfc7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts @@ -1,4 +1,4 @@ -import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const documentTypeName = 'TestDocumentType'; @@ -24,7 +24,7 @@ test('can create a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoU await umbracoUi.documentType.clickSaveButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy(); await umbracoUi.documentType.reloadTree('Document Types'); await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName); @@ -43,7 +43,7 @@ test('can create a document type with a template', {tag: '@smoke'}, async ({umbr await umbracoUi.documentType.clickSaveButton(); // Assert - // Checks if both the success notification for document Types and teh template are visible + // Checks if both the success notification for document Types and the template are visible await umbracoUi.documentType.doesSuccessNotificationsHaveCount(2); // Checks if the documentType contains the template const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName); @@ -67,7 +67,7 @@ test('can create a element type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi await umbracoUi.documentType.clickSaveButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy(); // Checks if the isElement is true const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName); @@ -87,7 +87,7 @@ test('can rename a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoU await umbracoUi.documentType.clickSaveButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeTruthy(); await umbracoUi.documentType.isDocumentTreeItemVisible(wrongName, false); await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName); @@ -108,7 +108,7 @@ test('can update the alias for a document type', async ({umbracoApi, umbracoUi}) await umbracoUi.documentType.clickSaveButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName, true); const documentTypeDataNew = await umbracoApi.documentType.getByName(documentTypeName); expect(documentTypeDataNew.alias).toBe(newAlias); @@ -126,7 +126,7 @@ test('can add an icon for a document type', {tag: '@smoke'}, async ({umbracoApi, await umbracoUi.documentType.clickSaveButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName); expect(documentTypeData.icon).toBe(bugIcon); await umbracoUi.documentType.isDocumentTreeItemVisible(documentTypeName, true); @@ -144,6 +144,6 @@ test('can delete a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoU await umbracoUi.documentType.clickDeleteAndConfirmButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeFalsy(); }); \ No newline at end of file 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 index a96999f4418c..dacd51f82a45 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeFolder.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const documentFolderName = 'TestFolder'; @@ -22,7 +22,7 @@ test('can create a empty document type folder', {tag: '@smoke'}, async ({umbraco await umbracoUi.documentType.clickCreateFolderButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const folder = await umbracoApi.documentType.getByName(documentFolderName); expect(folder.name).toBe(documentFolderName); // Checks if the folder is in the root @@ -41,7 +41,7 @@ test('can delete a document type folder', {tag: '@smoke'}, async ({umbracoApi, u await umbracoUi.documentType.deleteFolder(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); await umbracoApi.documentType.doesNameExist(documentFolderName); await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName, false); }); @@ -61,7 +61,7 @@ test('can rename a document type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.documentType.clickConfirmRenameFolderButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const folder = await umbracoApi.documentType.getByName(documentFolderName); expect(folder.name).toBe(documentFolderName); await umbracoUi.documentType.isDocumentTreeItemVisible(oldFolderName, false); @@ -84,7 +84,7 @@ test('can create a document type folder in a folder', async ({umbracoApi, umbrac await umbracoUi.documentType.clickCreateFolderButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const folder = await umbracoApi.documentType.getByName(childFolderName); expect(folder.name).toBe(childFolderName); // Checks if the parentFolder contains the ChildFolder as a child @@ -115,7 +115,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb await umbracoUi.documentType.clickCreateFolderButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.documentType.reloadTree(parentFolderName); await umbracoUi.documentType.isDocumentTreeItemVisible(documentFolderName); const grandParentChildren = await umbracoApi.documentType.getChildren(grandParentFolderId); 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 index 998843d9ca91..e008523c2d43 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeTemplatesTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeTemplatesTab.spec.ts @@ -73,4 +73,4 @@ test.skip('can remove an allowed template from a document type', async ({umbraco await umbracoUi.documentType.isSuccessNotificationVisible(); const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName); expect(documentTypeData.allowedTemplates).toHaveLength(0); -}); \ No newline at end of file +}); 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 9d756daab3bf..fb1e9c7a7a2c 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 {NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const languageName = 'Arabic'; @@ -25,7 +25,7 @@ test('can add language', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.language.doesExist(isoCode)).toBeTruthy(); // Verify the created language displays in the list await umbracoUi.language.clickLanguagesMenu(); @@ -44,7 +44,7 @@ test('can update default language option', {tag: '@smoke'}, async ({umbracoApi, await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.isDefault).toBe(true); @@ -67,7 +67,7 @@ test('can update mandatory language option', async ({umbracoApi, umbracoUi}) => await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.isMandatory).toBe(true); }); @@ -82,7 +82,7 @@ test('can delete language', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.language.removeLanguageByName(languageName); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.language.doesExist(isoCode)).toBeFalsy(); await umbracoUi.language.isLanguageNameVisible(languageName, false); }); @@ -99,7 +99,7 @@ test('can remove fallback language', async ({umbracoApi, umbracoUi}) => { await umbracoUi.language.clickSaveButton(); // Act - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.fallbackIsoCode).toBeFalsy(); }); @@ -117,7 +117,7 @@ test('can add fallback language', async ({umbracoApi, umbracoUi}) => { await umbracoUi.language.clickSaveButton(); // Act - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.fallbackIsoCode).toBe(defaultLanguageIsoCode); }); @@ -134,5 +134,5 @@ test('cannot add a language with duplicate ISO code', async ({umbracoApi, umbrac await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isErrorNotificationVisible(); + await umbracoUi.language.doesErrorNotificationHaveText(NotificationConstantHelper.error.duplicateISOcode); }); 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 index 96d562206eba..0690dac4ef08 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts @@ -1,5 +1,5 @@ import {expect} from "@playwright/test"; -import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; const mediaTypeName = 'TestMediaType'; @@ -22,7 +22,7 @@ test('can create a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy(); }); @@ -38,7 +38,7 @@ test('can rename a media type', async ({umbracoApi, umbracoUi}) => { await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy(); }); @@ -56,7 +56,7 @@ test('can update the alias for a media type', async ({umbracoApi, umbracoUi}) => await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); expect(mediaTypeData.alias).toBe(updatedAlias); }); @@ -72,7 +72,7 @@ test('can add an icon for a media type', {tag: '@smoke'}, async ({umbracoApi, um await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); expect(mediaTypeData.icon).toBe(bugIcon); await umbracoUi.mediaType.isTreeItemVisible(mediaTypeName, true); @@ -89,6 +89,6 @@ test('can delete a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.mediaType.clickConfirmToDeleteButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeFalsy(); }); 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 index 83006c9e54db..cea63f0f483f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const mediaTypeFolderName = 'TestMediaTypeFolder'; @@ -19,7 +19,7 @@ test('can create a empty media type folder', async ({umbracoApi, umbracoUi}) => await umbracoUi.mediaType.createFolder(mediaTypeFolderName); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const folder = await umbracoApi.mediaType.getByName(mediaTypeFolderName); expect(folder.name).toBe(mediaTypeFolderName); // Checks if the folder is in the root @@ -37,7 +37,7 @@ test('can delete a media type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.mediaType.deleteFolder(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeFolderName)).toBeFalsy(); }); @@ -55,7 +55,7 @@ test('can rename a media type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.mediaType.clickConfirmRenameFolderButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const folder = await umbracoApi.mediaType.getByName(mediaTypeFolderName); expect(folder.name).toBe(mediaTypeFolderName); }); @@ -72,6 +72,7 @@ test('can create a media type folder in a folder', async ({umbracoApi, umbracoUi await umbracoUi.mediaType.createFolder(childFolderName); // Assert + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName); await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true); const parentFolderChildren = await umbracoApi.mediaType.getChildren(parentFolderId); @@ -97,6 +98,7 @@ test('can create a media type folder in a folder in a folder', async ({umbracoAp await umbracoUi.mediaType.createFolder(childFolderName); // Assert + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.mediaType.clickCaretButtonForName(mediaTypeFolderName); await umbracoUi.mediaType.isTreeItemVisible(childFolderName, true); const grandParentFolderChildren = await umbracoApi.mediaType.getChildren(grandParentFolderId); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts index 71a2dcdb694b..f1155332f854 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const partialViewName = 'TestPartialView'; @@ -26,7 +26,7 @@ test('can create an empty partial view', {tag: '@smoke'}, async ({umbracoApi, um await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.partialView.doesNameExist(partialViewFileName)).toBeTruthy(); // Verify the new partial view is displayed under the Partial Views section await umbracoUi.partialView.isPartialViewRootTreeItemVisible(partialViewFileName); @@ -46,7 +46,7 @@ test('can create a partial view from snippet', async ({umbracoApi, umbracoUi}) = await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.partialView.doesExist(partialViewFileName)).toBeTruthy(); const partialViewData = await umbracoApi.partialView.getByName(partialViewFileName); @@ -80,7 +80,7 @@ test('can rename a partial view', {tag: '@smoke'}, async ({umbracoApi, umbracoUi await umbracoUi.partialView.rename(partialViewName); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.renamed); expect(await umbracoApi.partialView.doesNameExist(partialViewFileName)).toBeTruthy(); expect(await umbracoApi.partialView.doesNameExist(wrongPartialViewFileName)).toBeFalsy(); // Verify the old partial view is NOT displayed under the Partial Views section @@ -106,7 +106,7 @@ test('can update a partial view content', {tag: '@smoke'}, async ({umbracoApi, u await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const updatedPartialView = await umbracoApi.partialView.getByName(partialViewFileName); expect(updatedPartialView.content).toBe(updatedPartialViewContent); }); @@ -145,7 +145,7 @@ test('can use query builder with Order By statement for a partial view', async ( await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const updatedPartialView = await umbracoApi.partialView.getByName(partialViewFileName); expect(updatedPartialView.content).toBe(expectedTemplateContent); }); @@ -178,6 +178,7 @@ test('can use query builder with Where statement for a partial view', async ({um // Act await umbracoUi.partialView.openPartialViewAtRoot(partialViewFileName); + await umbracoUi.waitForTimeout(500); await umbracoUi.partialView.addQueryBuilderWithWhereStatement(propertyAliasValue, operatorValue, constrainValue); // Verify that the code is shown await umbracoUi.partialView.isQueryBuilderCodeShown(expectedCode); @@ -185,7 +186,7 @@ test('can use query builder with Where statement for a partial view', async ({um await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const updatedPartialView = await umbracoApi.partialView.getByName(partialViewFileName); expect(updatedPartialView.content).toBe(expectedTemplateContent); }); @@ -206,7 +207,7 @@ test('can insert dictionary item into a partial view', async ({umbracoApi, umbra await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const partialViewData = await umbracoApi.partialView.getByName(partialViewFileName); expect(partialViewData.content).toBe(partialViewContent); }); @@ -225,7 +226,7 @@ test('can insert value into a partial view', async ({umbracoApi, umbracoUi}) => await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const partialViewData = await umbracoApi.partialView.getByName(partialViewFileName); expect(partialViewData.content).toBe(partialViewContent); }); @@ -241,7 +242,7 @@ test('can delete a partial view', {tag: '@smoke'}, async ({umbracoApi, umbracoUi await umbracoUi.partialView.clickDeleteAndConfirmButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.partialView.doesExist(partialViewFileName)).toBeFalsy(); // Verify the partial view is NOT displayed under the Partial Views section await umbracoUi.partialView.clickRootFolderCaretButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts index 58741a3d7a57..239c9e1f2cdf 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialViewFolder.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const partialViewName = 'TestPartialView'; @@ -23,7 +23,7 @@ test('can create a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.partialView.createFolder(folderName); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.partialView.doesFolderExist(folderName)).toBeTruthy(); // Verify the partial view folder is displayed under the Partial Views section await umbracoUi.partialView.clickRootFolderCaretButton(); @@ -65,7 +65,7 @@ test('can create a partial view in a folder', async ({umbracoApi, umbracoUi}) => await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const childrenData = await umbracoApi.partialView.getChildren(folderPath); expect(childrenData[0].name).toEqual(partialViewFileName); // Verify the partial view is displayed in the folder under the Partial Views section @@ -94,7 +94,7 @@ test('can create a partial view in a folder in a folder', async ({umbracoApi, um await umbracoUi.partialView.clickSaveButton(); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); const childFolderChildrenData = await umbracoApi.partialView.getChildren(childFolderPath); expect(childFolderChildrenData[0].name).toEqual(partialViewFileName); @@ -114,7 +114,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.partialView.createFolder(childFolderName); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.partialView.doesNameExist(childFolderName)).toBeTruthy(); const partialViewChildren = await umbracoApi.partialView.getChildren('/' + folderName); expect(partialViewChildren[0].path).toBe('/' + folderName + '/' + childFolderName); @@ -137,7 +137,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb await umbracoUi.partialView.createFolder(childOfChildFolderName); // Assert - await umbracoUi.partialView.isSuccessNotificationVisible(); + await umbracoUi.partialView.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.partialView.doesNameExist(childOfChildFolderName)).toBeTruthy(); const partialViewChildren = await umbracoApi.partialView.getChildren('/' + folderName + '/' + childFolderName); expect(partialViewChildren[0].path).toBe('/' + folderName + '/' + childFolderName + '/' + childOfChildFolderName); @@ -158,5 +158,5 @@ test('cannot delete non-empty folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.partialView.deleteFolder(); // Assert - await umbracoUi.script.isErrorNotificationVisible(); + await umbracoUi.partialView.doesErrorNotificationHaveText(NotificationConstantHelper.error.notEmpty); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/Script.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/Script.spec.ts index 282a503021c1..902082cf196b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/Script.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/Script.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const scriptName = 'TestScript.js'; @@ -25,7 +25,7 @@ test('can create a empty script', {tag: '@smoke'}, async ({umbracoApi, umbracoUi await umbracoUi.script.clickSaveButton(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); await umbracoUi.script.isScriptRootTreeItemVisible(scriptName); }); @@ -44,7 +44,7 @@ test('can create a script with content', async ({umbracoApi, umbracoUi}) => { await umbracoUi.script.clickSaveButton(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); const scriptData = await umbracoApi.script.getByName(scriptName); expect(scriptData.content).toBe(scriptContent); @@ -63,7 +63,7 @@ test('can update a script', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.script.clickSaveButton(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const updatedScript = await umbracoApi.script.get(scriptPath); expect(updatedScript.content).toBe(updatedScriptContent); }); @@ -79,7 +79,7 @@ test('can delete a script', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.script.clickDeleteAndConfirmButton(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeFalsy(); await umbracoUi.script.isScriptRootTreeItemVisible(scriptName, false, false); }); @@ -96,7 +96,7 @@ test('can rename a script', async ({umbracoApi, umbracoUi}) => { await umbracoUi.script.rename(scriptName); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.renamed); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); expect(await umbracoApi.script.doesNameExist(wrongScriptName)).toBeFalsy(); }); @@ -104,7 +104,7 @@ test('can rename a script', async ({umbracoApi, umbracoUi}) => { test('cannot create a script with an empty name', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoUi.script.goToSection(ConstantHelper.sections.settings); - + // Act await umbracoUi.script.clickActionsMenuAtRoot(); await umbracoUi.script.clickCreateButton(); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts index ef99ac0e41a0..2676911ec2bb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Script/ScriptFolder.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const scriptName = 'TestScript.js'; @@ -23,7 +23,7 @@ test('can create a folder', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.waitForTimeout(1000); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeTruthy(); await umbracoUi.script.isScriptRootTreeItemVisible(scriptFolderName); }); @@ -39,7 +39,7 @@ test('can delete a folder', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.script.deleteFolder(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeFalsy(); await umbracoUi.script.isScriptRootTreeItemVisible(scriptFolderName, false, false); }); @@ -60,7 +60,7 @@ test('can create a script in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.script.clickSaveButton(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + scriptName); @@ -82,7 +82,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.script.createFolder(childFolderName); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesNameExist(childFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName); @@ -105,7 +105,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb await umbracoUi.script.createFolder(childOfChildFolderName); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesNameExist(childOfChildFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + childOfChildFolderName); @@ -130,7 +130,7 @@ test('can create a script in a folder in a folder', async ({umbracoApi, umbracoU await umbracoUi.script.clickSaveButton(); // Assert - await umbracoUi.script.isSuccessNotificationVisible(); + await umbracoUi.script.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + scriptName); @@ -151,5 +151,5 @@ test('cannot delete non-empty folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.script.deleteFolder(); // Assert - await umbracoUi.script.isErrorNotificationVisible(); + await umbracoUi.script.doesErrorNotificationHaveText(NotificationConstantHelper.error.notEmpty); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/Stylesheet.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/Stylesheet.spec.ts index 43ee3af2d75c..bd6de2872563 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/Stylesheet.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/Stylesheet.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const stylesheetName = 'TestStyleSheetFile.css'; @@ -27,7 +27,7 @@ test('can create a empty stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbra await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName); }); @@ -46,7 +46,7 @@ test('can create a stylesheet with content', async ({umbracoApi, umbracoUi}) => await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(stylesheetContent); @@ -67,7 +67,7 @@ test.skip('can create a new Rich Text Editor stylesheet file', {tag: '@smoke'}, await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesExist(stylesheetName)).toBeTruthy(); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(stylesheetContent); @@ -87,7 +87,7 @@ test.skip('can update a stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbrac await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(stylesheetContent); }); @@ -103,7 +103,7 @@ test('can delete a stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.stylesheet.clickDeleteAndConfirmButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeFalsy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName, false, false); }); @@ -121,7 +121,7 @@ test('can rename a stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.stylesheet.rename(stylesheetName); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.renamed); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); expect(await umbracoApi.stylesheet.doesNameExist(wrongStylesheetName)).toBeFalsy(); }); @@ -143,7 +143,7 @@ test('can edit rich text editor styles', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(newStylesheetContent); }); @@ -161,7 +161,7 @@ test('can remove rich text editor styles', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(''); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts index 30e01aebda97..8871837fceb5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const stylesheetName = 'TestStyleSheetFile.css'; @@ -23,7 +23,7 @@ test('can create a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.waitForTimeout(1000); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeTruthy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetFolderName); }); @@ -39,7 +39,7 @@ test('can delete a folder', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.stylesheet.deleteFolder(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeFalsy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetFolderName, false, false); }); @@ -56,7 +56,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.createFolder(childFolderName); //Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(childFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName); @@ -79,7 +79,7 @@ test('can create a folder in a folder in a folder', {tag: '@smoke'}, async ({umb await umbracoUi.stylesheet.createFolder(childOfChildFolderName); //Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(childOfChildFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName + '/' + childFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + childOfChildFolderName); @@ -103,7 +103,7 @@ test('can create a stylesheet in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); const stylesheetChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName); expect(stylesheetChildren[0].path).toBe('/' + stylesheetFolderName + '/' + stylesheetName); @@ -132,7 +132,7 @@ test('can create a stylesheet in a folder in a folder', async ({umbracoApi, umbr await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); const stylesheetChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName + '/' + childFolderName); expect(stylesheetChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + stylesheetName); @@ -155,5 +155,5 @@ test('cannot delete non-empty folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.deleteFolder(); //Assert - await umbracoUi.stylesheet.isErrorNotificationVisible(); -}); \ No newline at end of file + await umbracoUi.stylesheet.doesErrorNotificationHaveText(NotificationConstantHelper.error.notEmpty); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts index fcb89f59c9b1..106d2102f465 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Template/Templates.spec.ts @@ -1,4 +1,4 @@ -import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const templateName = 'TestTemplate'; @@ -24,7 +24,7 @@ test('can create a template', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) = await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.template.doesNameExist(templateName)).toBeTruthy(); await umbracoUi.template.isTemplateRootTreeItemVisible(templateName); }); @@ -38,11 +38,12 @@ test('can update content of a template', {tag: '@smoke'}, async ({umbracoApi, um // Act await umbracoUi.template.goToTemplate(templateName); + await umbracoUi.template.enterTemplateContent(''); await umbracoUi.template.enterTemplateContent(updatedTemplateContent); await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); // Checks if the template was updated const updatedTemplate = await umbracoApi.template.getByName(templateName); expect(updatedTemplate.content).toBe(updatedTemplateContent); @@ -62,7 +63,7 @@ test('can rename a template', async ({umbracoApi, umbracoUi}) => { await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.get(templateId); expect(templateData.name).toBe(templateName); }); @@ -78,7 +79,7 @@ test('can delete a template', async ({umbracoApi, umbracoUi}) => { await umbracoUi.template.clickDeleteAndConfirmButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.template.doesNameExist(templateName)).toBeFalsy(); await umbracoUi.template.isTemplateRootTreeItemVisible(templateName, false); }); @@ -98,7 +99,7 @@ test('can set a template as master template', async ({umbracoApi, umbracoUi}) => await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.template.isMasterTemplateNameVisible(templateName); // Checks if the childTemplate has the masterTemplate set const childTemplateData = await umbracoApi.template.getByName(childTemplateName); @@ -125,7 +126,7 @@ test('can remove a master template', async ({umbracoApi, umbracoUi}) => { await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.template.isMasterTemplateNameVisible('No master'); const childTemplate = await umbracoApi.template.getByName(childTemplateName); expect(childTemplate.masterTemplate).toBe(null); @@ -169,7 +170,7 @@ test.skip('can use query builder with Order By statement for a template', async await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(expectedTemplateContent); }); @@ -201,6 +202,7 @@ test('can use query builder with Where statement for a template', async ({umbrac // Act await umbracoUi.template.goToTemplate(templateName); + await umbracoUi.waitForTimeout(500); await umbracoUi.template.addQueryBuilderWithWhereStatement(propertyAliasValue, operatorValue, constrainValue); // Verify that the code is shown await umbracoUi.template.isQueryBuilderCodeShown(expectedCode); @@ -208,7 +210,7 @@ test('can use query builder with Where statement for a template', async ({umbrac await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(expectedTemplateContent); }); @@ -227,7 +229,7 @@ test('can insert sections - render child template into a template', async ({umbr await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(templateContent); }); @@ -246,7 +248,7 @@ test('can insert sections - render a named section into a template', async ({umb await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(templateContent); }); @@ -284,7 +286,7 @@ test('can insert dictionary item into a template', async ({umbracoApi, umbracoUi await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(templateContent); @@ -307,7 +309,7 @@ test('can insert partial view into a template', async ({umbracoApi, umbracoUi}) await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(templateContent); }); @@ -324,7 +326,7 @@ test('can insert value into a template', async ({umbracoApi, umbracoUi}) => { await umbracoUi.template.clickSaveButton(); // Assert - await umbracoUi.template.isSuccessNotificationVisible(); + await umbracoUi.template.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const templateData = await umbracoApi.template.getByName(templateName); expect(templateData.content).toBe(templateContent); }); @@ -368,5 +370,7 @@ test('cannot create a template with an empty name', {tag: '@smoke'}, async ({umb // Assert await umbracoUi.template.isErrorNotificationVisible(); + // TODO: Uncomment this when the front-end updates the error message + //await umbracoUi.template.doesErrorNotificationHaveText(NotificationConstantHelper.error.emptyName); expect(await umbracoApi.template.doesNameExist(templateName)).toBeFalsy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts new file mode 100644 index 000000000000..f59ae83870b0 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts @@ -0,0 +1,89 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +const rootDocumentTypeName = 'RootDocumentType'; +const childDocumentTypeOneName = 'ChildDocumentTypeOne'; +const childDocumentTypeTwoName = 'ChildDocumentTypeTwo'; +let childDocumentTypeOneId = null; +let rootDocumentTypeId = null; +const rootDocumentName = 'RootDocument'; +const childDocumentOneName = 'ChildDocumentOne'; +const childDocumentTwoName = 'ChildDocumentTwo'; +let rootDocumentId = null; +let childDocumentOneId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeOneName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeTwoName); + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + childDocumentTypeOneId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeOneName); + const childDocumentTypeTwoId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeTwoName); + rootDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedTwoChildNodes(rootDocumentTypeName, childDocumentTypeOneId, childDocumentTypeTwoId); + rootDocumentId = await umbracoApi.document.createDefaultDocument(rootDocumentName, rootDocumentTypeId); + childDocumentOneId = await umbracoApi.document.createDefaultDocumentWithParent(childDocumentOneName, childDocumentTypeOneId, rootDocumentId); + await umbracoApi.document.createDefaultDocumentWithParent(childDocumentTwoName, childDocumentTypeTwoId, rootDocumentId); + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeOneName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeTwoName); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can see root start node and children', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [rootDocumentId]); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName); +}); + +test('can see parent of start node but not access it', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [childDocumentOneId]); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); + await umbracoUi.content.goToContentWithName(rootDocumentName); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); +}); + +test('can not see any content when no start nodes specified', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isDocumentTreeEmpty(); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts new file mode 100644 index 000000000000..81cdeb84ed70 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts @@ -0,0 +1,83 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +let rootFolderId = null; +let childFolderOneId = null; +const rootFolderName = 'RootFolder'; +const childFolderOneName = 'ChildFolderOne'; +const childFolderTwoName = 'ChildFolderTwo'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.media.ensureNameNotExists(rootFolderName); + await umbracoApi.media.ensureNameNotExists(childFolderOneName); + await umbracoApi.media.ensureNameNotExists(childFolderTwoName); + rootFolderId = await umbracoApi.media.createDefaultMediaFolder(rootFolderName); + childFolderOneId = await umbracoApi.media.createDefaultMediaFolderAndParentId(childFolderOneName, rootFolderId); + await umbracoApi.media.createDefaultMediaFolderAndParentId(childFolderTwoName, rootFolderId); + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithMediaSection(userGroupName); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.media.ensureNameNotExists(rootFolderName); + await umbracoApi.media.ensureNameNotExists(childFolderOneName); + await umbracoApi.media.ensureNameNotExists(childFolderTwoName); +}); + +test('can see root media start node and children', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], false, [rootFolderId]); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.media, false); + + // Assert + await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); + await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName); +}); + +test('can see parent of start node but not access it', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], false, [childFolderOneId]); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.media, false); + + // Assert + await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); + await umbracoUi.waitForTimeout(500); + await umbracoUi.media.goToMediaWithName(rootFolderName); + await umbracoUi.media.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); +}); + +test('can not see any media when no media start nodes specified', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.media, false); + + // Assert + await umbracoUi.media.isMediaTreeItemVisible(rootFolderName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/UICulture.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/UICulture.spec.ts new file mode 100644 index 000000000000..ede78fc9e8c0 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/UICulture.spec.ts @@ -0,0 +1,44 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can see correct translation for content in english', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], true, [], false, 'en-us'); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + + // Act + await umbracoUi.goToBackOffice(); + + // Assert + await umbracoUi.user.isSectionWithNameVisible(ConstantHelper.sections.content, true); +}); + +test('can see correct translation for content in danish', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], true, [], false, 'da-dk'); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + + // Act + await umbracoUi.goToBackOffice(); + + // Assert + // Indhold is the Danish translation of Content + await umbracoUi.user.isSectionWithNameVisible('Indhold', true); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts new file mode 100644 index 000000000000..7c7c48d18661 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts @@ -0,0 +1,92 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +const rootDocumentTypeName = 'RootDocumentType'; +const childDocumentTypeOneName = 'ChildDocumentTypeOne'; +const childDocumentTypeTwoName = 'ChildDocumentTypeTwo'; +let childDocumentTypeOneId = null; +let childDocumentTypeTwoId = null; +let rootDocumentTypeId = null; +const rootDocumentName = 'RootDocument'; +const childDocumentOneName = 'ChildDocumentOne'; +const childDocumentTwoName = 'ChildDocumentTwo'; +let rootDocumentId = null; +let childDocumentOneId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeOneName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeTwoName); + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + childDocumentTypeOneId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeOneName); + childDocumentTypeTwoId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeTwoName); + rootDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedTwoChildNodes(rootDocumentTypeName, childDocumentTypeOneId, childDocumentTypeTwoId); + rootDocumentId = await umbracoApi.document.createDefaultDocument(rootDocumentName, rootDocumentTypeId); + childDocumentOneId = await umbracoApi.document.createDefaultDocumentWithParent(childDocumentOneName, childDocumentTypeOneId, rootDocumentId); + await umbracoApi.document.createDefaultDocumentWithParent(childDocumentTwoName, childDocumentTypeTwoId, rootDocumentId); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeOneName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeTwoName); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can see root start node and children', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithDocumentStartNode(userGroupName, rootDocumentId); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName); +}); + +test('can see parent of start node but not access it', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithDocumentStartNode(userGroupName, childDocumentOneId); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); + await umbracoUi.content.goToContentWithName(rootDocumentName); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentOneName); + await umbracoUi.content.isChildContentInTreeVisible(rootDocumentName, childDocumentTwoName, false); +}); + +test('can not see any content when no start nodes specified', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isDocumentTreeEmpty(); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentWithRichTextEditor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentWithRichTextEditor.spec.ts new file mode 100644 index 000000000000..df9655388a71 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentWithRichTextEditor.spec.ts @@ -0,0 +1,52 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +let userGroupId = null; + +const documentTypeName = 'TestDocumentType'; +const documentName = 'TestDocument'; +const richTextEditorName = 'TestRichTextEditor'; +const stylesheetName = 'TestStylesheet.css'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); + const stylesheetPath = await umbracoApi.stylesheet.createStylesheetWithHeaderContent(stylesheetName); + const dataTypeId = await umbracoApi.dataType.createRichTextEditorDataTypeWithStylesheet(richTextEditorName, stylesheetPath); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, richTextEditorName, dataTypeId); + const userGroup = await umbracoApi.userGroup.getByName('Editors'); + userGroupId = userGroup.id; +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); + await umbracoApi.dataType.ensureNameNotExists(richTextEditorName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with a rich text editor that has a stylesheet', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(documentName); + // Is needed to make sure that the rich text editor is loaded + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource, false); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + expect(await umbracoApi.document.doesNameExist(documentName)).toBeTruthy(); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts new file mode 100644 index 000000000000..257ac705423d --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts @@ -0,0 +1,619 @@ +import {ConstantHelper, NotificationConstantHelper, test} from "@umbraco/playwright-testhelpers"; +import {expect} from "@playwright/test"; + +const rootDocumentTypeName = 'RootDocumentType'; +const childDocumentTypeOneName = 'ChildDocumentTypeOne'; +const childDocumentTypeTwoName = 'ChildDocumentTypeTwo'; +let childDocumentTypeId = null; +let rootDocumentTypeId = null; +const rootDocumentName = 'RootDocument'; +const childDocumentOneName = 'ChildDocumentOne'; +const childDocumentTwoName = 'SecondChildDocument'; +let rootDocumentId = null; + +const dataTypeName = 'Textstring'; +let dataTypeId = null; +const documentText = 'This is test document text'; + +const testDocumentName = 'TestDocument'; +const documentBlueprintName = 'TestBlueprintName'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeOneName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeTwoName); + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.documentBlueprint.ensureNameNotExists(documentBlueprintName); + const dataType = await umbracoApi.dataType.getByName(dataTypeName); + dataTypeId = dataType.id; + childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeOneName); + rootDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndDataType(rootDocumentTypeName, childDocumentTypeId, dataTypeName, dataTypeId); + rootDocumentId = await umbracoApi.document.createDocumentWithTextContent(rootDocumentName, rootDocumentTypeId, documentText, dataTypeName); + await umbracoApi.document.createDefaultDocumentWithParent(childDocumentOneName, childDocumentTypeId, rootDocumentId); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeOneName); + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeTwoName); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.documentBlueprint.ensureNameNotExists(documentBlueprintName); +}); + +test('can browse content node with permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithBrowseNodePermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.goToContentWithName(rootDocumentName); + + // Assert + await umbracoUi.content.doesDocumentHaveName(rootDocumentName); +}); + +test('can not browse content node with permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithBrowseNodePermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.goToContentWithName(rootDocumentName); + + // Assert + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); +}); + +test('can create document blueprint with permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithCreateDocumentBlueprintPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickCreateDocumentBlueprintButton(); + await umbracoUi.content.enterDocumentBlueprintName(documentBlueprintName); + await umbracoUi.content.clickSaveDocumentBlueprintButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.documentBlueprintCreated); +}); + +test('can not create document blueprint with permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithCreateDocumentBlueprintPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.documentBlueprint.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can delete content with delete permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithDeleteDocumentPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickTrashButton(); + await umbracoUi.content.clickConfirmTrashButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.movedToRecycleBin); +}); + +test('can not delete content with delete permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithDeleteDocumentPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can empty recycle bin with delete permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.document.moveToRecycleBin(rootDocumentId); + userGroupId = await umbracoApi.userGroup.createUserGroupWithDeleteDocumentPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickRecycleBinButton(); + await umbracoUi.content.clickEmptyRecycleBinButton(); + await umbracoUi.content.clickConfirmEmptyRecycleBinButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.emptiedRecycleBin); +}); + +test('can not empty recycle bin with delete permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.document.moveToRecycleBin(rootDocumentId); + userGroupId = await umbracoApi.userGroup.createUserGroupWithDeleteDocumentPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForRecycleBinVisible(false); +}); + +test('can create content with create permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithCreateDocumentPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(rootDocumentTypeName); + await umbracoUi.content.enterContentName(testDocumentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); +}); + +test('can not create content with create permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithCreateDocumentPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +// TODO: Setup SMTP server to test notifications, do this when we test appsettings.json +test.skip('can create notifications with notification permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithNotificationsPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); +}); + +test('can not create notifications with notification permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithNotificationsPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can publish content with publish permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPublishPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickPublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.published); + expect(await umbracoApi.document.isDocumentPublished(rootDocumentId)).toBeTruthy(); +}); + +test('can not publish content with publish permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPublishPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +// Bug, does nothing in the frontend. +test.skip('can set permissions with set permissions permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithSetPermissionsPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + // await umbracoUi.content.clickSetPermissionsButton(); + // + // // Assert + // await umbracoUi.content.doesDocumentPermissionsDialogExist(); +}); + +test('can not set permissions with set permissions permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithSetPermissionsPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can unpublish content with unpublish permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.document.publish(rootDocumentId); + expect(await umbracoApi.document.isDocumentPublished(rootDocumentId)).toBeTruthy(); + userGroupId = await umbracoApi.userGroup.createUserGroupWithUnpublishPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickUnpublishButton(); + await umbracoUi.content.clickConfirmToUnpublishButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.unpublished); + expect(await umbracoApi.document.isDocumentPublished(rootDocumentId)).toBeFalsy(); +}); + +test('can not unpublish content with unpublish permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.document.publish(rootDocumentId); + expect(await umbracoApi.document.isDocumentPublished(rootDocumentId)).toBeTruthy(); + userGroupId = await umbracoApi.userGroup.createUserGroupWithUnpublishPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can update content with update permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithUpdatePermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.goToContentWithName(rootDocumentName); + await umbracoUi.content.isDocumentReadOnly(false); + await umbracoUi.content.enterContentName(testDocumentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.document.doesNameExist(testDocumentName)).toBeTruthy(); +}); + +// TODO: the permission for update is not working, it is always enabled. +test.skip('can not update content with update permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithUpdatePermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can duplicate content with duplicate permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const duplicatedContentName = rootDocumentName + ' (1)'; + userGroupId = await umbracoApi.userGroup.createUserGroupWithDuplicatePermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + // Duplicate to root + await umbracoUi.content.clickDuplicateToButton(); + await umbracoUi.content.clickLabelWithName('Content'); + await umbracoUi.content.clickDuplicateButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.duplicated); + expect(await umbracoApi.document.doesNameExist(rootDocumentName)).toBeTruthy(); + expect(await umbracoApi.document.doesNameExist(duplicatedContentName)).toBeTruthy(); + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); + await umbracoUi.content.isContentInTreeVisible(duplicatedContentName); + const rootContent = await umbracoApi.document.getByName(rootDocumentName); + const rootDuplicatedContent = await umbracoApi.document.getByName(duplicatedContentName); + expect(rootContent.values[0].value).toEqual(rootDuplicatedContent.values[0].value); +}); + +test('can not duplicate content with duplicate permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithDuplicatePermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can move content with move to permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const moveToDocumentName = 'SecondRootDocument'; + const moveToDocumentId = await umbracoApi.document.createDocumentWithTextContent(moveToDocumentName, rootDocumentTypeId, documentText, dataTypeName); + userGroupId = await umbracoApi.userGroup.createUserGroupWithMoveToPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.clickActionsMenuForContent(childDocumentOneName); + await umbracoUi.content.clickMoveToButton(); + await umbracoUi.content.moveToContentWithName([], moveToDocumentName); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.moved); + await umbracoUi.content.reloadContentTree(); + await umbracoUi.content.isCaretButtonVisibleForContentName(moveToDocumentName, true); + await umbracoUi.content.clickCaretButtonForContentName(moveToDocumentName); + await umbracoUi.content.isChildContentInTreeVisible(moveToDocumentName, childDocumentOneName, true); + await umbracoUi.content.isCaretButtonVisibleForContentName(rootDocumentName, false); + expect(await umbracoApi.document.getChildrenAmount(rootDocumentId)).toEqual(0); + expect(await umbracoApi.document.getChildrenAmount(moveToDocumentId)).toEqual(1); +}); + +test('can not move content with move to permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const moveToDocumentName = 'SecondRootDocument'; + await umbracoApi.document.createDocumentWithTextContent(moveToDocumentName, rootDocumentTypeId, documentText, dataTypeName); + userGroupId = await umbracoApi.userGroup.createUserGroupWithMoveToPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can sort children with sort children permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.document.createDefaultDocumentWithParent(childDocumentTwoName, childDocumentTypeId, rootDocumentId); + userGroupId = await umbracoApi.userGroup.createUserGroupWithSortChildrenPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickSortChildrenButton(); + + // TODO: uncomment when it is not flaky + // const childDocumentOneLocator = await umbracoUi.content.getButtonWithName(childDocumentOneName); + // const childDocumentTwoLocator = await umbracoUi.content.getButtonWithName(childDocumentTwoName) + // await umbracoUi.content.sortChildrenDragAndDrop(childDocumentOneLocator, childDocumentTwoLocator, 10, 0, 10); + await umbracoUi.content.clickSortButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.itemsSorted); + // TODO: uncomment when it is not flaky + // await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + // await umbracoUi.content.doesIndexDocumentInTreeContainName(rootDocumentName, childDocumentTwoName, 0); + // await umbracoUi.content.doesIndexDocumentInTreeContainName(rootDocumentName, childDocumentOneName, 1); +}); + +test('can not sort children with sort children permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.document.createDefaultDocumentWithParent(childDocumentTwoName, childDocumentTypeId, rootDocumentId); + userGroupId = await umbracoApi.userGroup.createUserGroupWithSortChildrenPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can set culture and hostnames with culture and hostnames permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithCultureAndHostnamesPermission(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickCultureAndHostnamesButton(); + await umbracoUi.content.clickAddNewDomainButton(); + await umbracoUi.content.enterDomain('/en'); + await umbracoUi.content.clickSaveModalButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.culturesAndHostnamesSaved); +}); + +test('can not set culture and hostnames with culture and hostnames permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithCultureAndHostnamesPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +// TODO: Notification is not correct 'Public acccess setting created' should be 'access' +test.skip('can set public access with public access permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPublicAccessPermission(userGroupName); + const testMemberGroup = 'TestMemberGroup'; + await umbracoApi.memberGroup.ensureNameNotExists(testMemberGroup); + await umbracoApi.memberGroup.create(testMemberGroup) + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + await umbracoUi.content.clickPublicAccessButton(); + await umbracoUi.content.addGroupBasedPublicAccess(testMemberGroup, rootDocumentName); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.publicAccessSettingCreated); +}); + +test('can not set public access with public access permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithPublicAccessPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can rollback content with rollback permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithRollbackPermission(userGroupName); + await umbracoApi.document.publish(rootDocumentId); + const updatedTextStringText = 'This is an updated textString text'; + const content = await umbracoApi.document.get(rootDocumentId); + content.values[0].value = updatedTextStringText; + await umbracoApi.document.update(rootDocumentId, content); + await umbracoApi.document.publish(rootDocumentId); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.goToContentWithName(rootDocumentName); + await umbracoUi.content.doesDocumentPropertyHaveValue(dataTypeName, updatedTextStringText); + await umbracoUi.content.clickInfoTab(); + // Needs to wait for the rollback button to be visible + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.clickRollbackButton(); + await umbracoUi.content.clickLatestRollBackItem(); + await umbracoUi.content.clickRollbackContainerButton(); + + // Assert + await umbracoUi.content.doesDocumentPropertyHaveValue(dataTypeName, documentText); +}); + +test('can not rollback content with rollback permission disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithRollbackPermission(userGroupName, false); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Assert + await umbracoUi.content.isActionsMenuForNameVisible(rootDocumentName, false); +}); + +test('can not see delete button in content for userGroup with delete permission disabled and create permission enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithDeletePermissionAndCreatePermission(userGroupName, false, true); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.clickActionsMenuForContent(rootDocumentName); + + // Assert + await umbracoUi.content.isPermissionInActionsMenuVisible('Delete...', false); + await umbracoUi.content.isPermissionInActionsMenuVisible('Create...', true); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts new file mode 100644 index 000000000000..7b535fb067bc --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts @@ -0,0 +1,135 @@ +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +const documentTypeName = 'TestDocumentType'; +const documentName = 'TestDocument'; +const englishDocumentName = 'EnglishDocument'; +const danishDocumentName = 'DanishDocument'; +const vietnameseDocumentName = 'VietnameseDocument'; +let documentTypeId = null; + +const dataTypeName = 'Textstring'; +let dataTypeId = null; + +const englishIsoCode = 'en-US'; +const danishIsoCode = 'da'; +const vietnameseIsoCode = 'vi'; +const englishLanguageName = 'English (United States)'; +const danishLanguageName = 'Danish'; +const vietnameseLanguageName = 'Vietnamese'; +const cultureVariants = [ + { + isoCode: englishIsoCode, + name: englishDocumentName, + value: 'EnglishValue', + }, + { + isoCode: danishIsoCode, + name: danishDocumentName, + value: 'DanishValue', + }, + { + isoCode: vietnameseIsoCode, + name: vietnameseDocumentName, + value: 'VietnameseValue', + } +]; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.language.ensureIsoCodeNotExists(danishIsoCode); + await umbracoApi.language.ensureIsoCodeNotExists(vietnameseIsoCode); + await umbracoApi.language.createDanishLanguage(); + await umbracoApi.language.createVietnameseLanguage(); + const dataType = await umbracoApi.dataType.getByName(dataTypeName); + dataTypeId = dataType.id; + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId, 'TestGroup', true); + await umbracoApi.document.createDocumentWithMultipleVariants(documentName, documentTypeId, AliasHelper.toAlias(dataTypeName), cultureVariants); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.language.ensureIsoCodeNotExists(danishIsoCode); + await umbracoApi.language.ensureIsoCodeNotExists(vietnameseIsoCode); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can rename content with language set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + const updatedContentName = 'UpdatedContentName'; + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], false, [], false, englishIsoCode); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(englishDocumentName); + + // Act + await umbracoUi.content.isDocumentReadOnly(false); + await umbracoUi.content.enterContentName(updatedContentName); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.isContentInTreeVisible(updatedContentName); +}); + +test('can not rename content with language not set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], false, [], false, englishIsoCode); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.doesDocumentSectionHaveLanguageSelected(englishLanguageName); + await umbracoUi.content.changeDocumentSectionLanguage(danishLanguageName); + + // Act + await umbracoUi.content.goToContentWithName(danishDocumentName); + + // Assert + await umbracoUi.content.isDocumentReadOnly(); + await umbracoUi.content.isDocumentNameInputEditable(false); +}); + +test('can update content property with language set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.doesDocumentSectionHaveLanguageSelected(englishLanguageName); + + // Act + await umbracoUi.content.goToContentWithName(englishDocumentName); + + // Assert + await umbracoUi.content.isDocumentPropertyEditable(dataTypeName, true); +}); + +test('can not update content property with language not set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.doesDocumentSectionHaveLanguageSelected(englishLanguageName); + await umbracoUi.content.changeDocumentSectionLanguage(vietnameseLanguageName); + + // Act + await umbracoUi.content.goToContentWithName(vietnameseDocumentName); + + // Assert + await umbracoUi.content.isDocumentPropertyEditable(dataTypeName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts new file mode 100644 index 000000000000..b129c3979e56 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts @@ -0,0 +1,85 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +let rootFolderId = null; +let childFolderOneId = null; +const rootFolderName = 'RootFolder'; +const childFolderOneName = 'ChildFolderOne'; +const childFolderTwoName = 'ChildFolderTwo'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.media.ensureNameNotExists(rootFolderName); + await umbracoApi.media.ensureNameNotExists(childFolderOneName); + await umbracoApi.media.ensureNameNotExists(childFolderTwoName); + rootFolderId = await umbracoApi.media.createDefaultMediaFolder(rootFolderName); + childFolderOneId = await umbracoApi.media.createDefaultMediaFolderAndParentId(childFolderOneName, rootFolderId); + await umbracoApi.media.createDefaultMediaFolderAndParentId(childFolderTwoName, rootFolderId); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.media.ensureNameNotExists(rootFolderName); + await umbracoApi.media.ensureNameNotExists(childFolderOneName); + await umbracoApi.media.ensureNameNotExists(childFolderTwoName); +}); + +test('can see root media start node and children', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithMediaStartNode(userGroupName, rootFolderId); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.media, false); + + // Assert + await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); + await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName); +}); + +test('can see parent of start node but not access it', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithMediaStartNode(userGroupName, childFolderOneId); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.media, false); + + // Assert + await umbracoUi.media.isMediaTreeItemVisible(rootFolderName); + await umbracoUi.waitForTimeout(500); + await umbracoUi.media.goToMediaWithName(rootFolderName); + await umbracoUi.media.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource); + await umbracoUi.media.clickCaretButtonForMediaName(rootFolderName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderOneName); + await umbracoUi.media.isChildMediaVisible(rootFolderName, childFolderTwoName, false); +}); + +test('can not see any media when no media start nodes specified', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithMediaSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.user.goToSection(ConstantHelper.sections.media, false); + + // Assert + await umbracoUi.media.isMediaTreeItemVisible(rootFolderName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MemberSection.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MemberSection.spec.ts new file mode 100644 index 000000000000..ebd1f6069931 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MemberSection.spec.ts @@ -0,0 +1,99 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +let memberId = ''; +let memberTypeId = ''; +const memberName = 'Test Member'; +const memberTypeName = 'Test Member Type'; +const comment = 'This is test comment'; +const username = 'testmember'; +const email = 'testmember@acceptance.test'; +const password = '0123456789'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.memberType.ensureNameNotExists(memberTypeName); + await umbracoApi.member.ensureNameNotExists(memberName); + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.memberType.ensureNameNotExists(memberTypeName); + await umbracoApi.member.ensureNameNotExists(memberName); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can access members section with section enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithMemberSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], true, [], false, 'en-us'); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + + // Act + await umbracoUi.member.goToSection(ConstantHelper.sections.members, false); + + // Assert + await umbracoUi.user.isSectionWithNameVisible(ConstantHelper.sections.content, false); + await umbracoUi.member.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource, false); +}); + +// TODO: unskip when member creation is fixed +test.skip('can create member with members section set', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithMemberSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], true, [], false, 'en-us'); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.member.goToSection(ConstantHelper.sections.members, false); + await umbracoUi.member.clickMembersMenu(); + + // Act + await umbracoUi.member.clickCreateButton(); + await umbracoUi.member.enterMemberName(memberName); + await umbracoUi.member.clickInfoTab(); + await umbracoUi.member.enterUsername(username); + await umbracoUi.member.enterEmail(email); + await umbracoUi.member.enterPassword(password); + await umbracoUi.member.enterConfirmPassword(password); + await umbracoUi.member.clickDetailsTab(); + await umbracoUi.member.enterComments(comment); + await umbracoUi.member.clickSaveButton(); + + // Assert + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + await umbracoUi.member.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource, false); + expect(await umbracoApi.member.doesNameExist(memberName)).toBeTruthy(); +}); + +// TODO: unskip when member creation is fixed +test.skip('can update member with members section set', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithMemberSection(userGroupName); + memberTypeId = await umbracoApi.memberType.createDefaultMemberType(memberTypeName); + memberId = await umbracoApi.member.createDefaultMember(memberName, memberTypeId, email, username, password); + const updatedUsername = 'updatedusername'; + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], true, [], false, 'en-us'); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.member.goToSection(ConstantHelper.sections.members, false); + + // Act + await umbracoUi.member.clickMemberLinkByName(memberName); + await umbracoUi.member.enterUsername(updatedUsername); + await umbracoUi.member.clickSaveButton(); + + // Assert + await umbracoUi.member.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.member.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource, false); + const memberData = await umbracoApi.member.get(memberId); + expect(memberData.username).toBe(updatedUsername); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts new file mode 100644 index 000000000000..0932ab7f622b --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts @@ -0,0 +1,51 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can go to section defined in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + + // Act + await umbracoUi.goToBackOffice(); + + // Assert + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.content); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); +}); + +test('can not see section that is not defined in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + + // Act + await umbracoUi.goToBackOffice(); + + // Assert + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.content); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.media, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.settings, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.users, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.members, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.dictionary, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.packages, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts index a1b2494350e7..cee0518f9c8e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts @@ -1,16 +1,19 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const nameOfTheUser = 'TestUser'; const userEmail = 'TestUser@EmailTest.test'; const defaultUserGroupName = 'Writers'; +let userCount = null; test.beforeEach(async ({umbracoUi, umbracoApi}) => { await umbracoUi.goToBackOffice(); await umbracoApi.user.ensureNameNotExists(nameOfTheUser); }); -test.afterEach(async ({umbracoApi}) => { +test.afterEach(async ({umbracoApi, umbracoUi}) => { + // Waits so we can try to avoid db locks + await umbracoUi.waitForTimeout(500); await umbracoApi.user.ensureNameNotExists(nameOfTheUser); }); @@ -29,7 +32,7 @@ test('can create a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickCreateUserButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.user.doesNameExist(nameOfTheUser)).toBeTruthy(); }); @@ -47,7 +50,7 @@ test('can rename a user', async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesNameExist(nameOfTheUser)).toBeTruthy(); }); @@ -64,7 +67,7 @@ test('can delete a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickConfirmToDeleteButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.user.doesNameExist(nameOfTheUser)).toBeFalsy(); // Checks if the user is deleted from the list await umbracoUi.user.clickUsersMenu(); @@ -87,8 +90,7 @@ test('can add multiple user groups to a user', async ({umbracoApi, umbracoUi}) = await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); - await umbracoApi.user.getByName(nameOfTheUser); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainUserGroupIds(nameOfTheUser, [userGroupWriters.id, userGroupTranslators.id])).toBeTruthy(); }); @@ -105,7 +107,7 @@ test('can remove a user group from a user', {tag: '@smoke'}, async ({umbracoApi, await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.userGroupIds).toEqual([]); }); @@ -123,7 +125,7 @@ test('can update culture for a user', async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.languageIsoCode).toEqual(danishIsoCode); }); @@ -148,7 +150,7 @@ test('can add a content start node to a user', {tag: '@smoke'}, async ({umbracoA await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainContentStartNodeIds(nameOfTheUser, [documentId])).toBeTruthy(); // Clean @@ -183,7 +185,7 @@ test('can add multiple content start nodes for a user', async ({umbracoApi, umbr await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainContentStartNodeIds(nameOfTheUser, [documentId, secondDocumentId])).toBeTruthy(); // Clean @@ -216,7 +218,7 @@ test('can remove a content start node from a user', {tag: '@smoke'}, async ({umb await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainContentStartNodeIds(nameOfTheUser, [documentId])).toBeFalsy(); // Clean @@ -241,7 +243,7 @@ test('can add media start nodes for a user', {tag: '@smoke'}, async ({umbracoApi await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainMediaStartNodeIds(nameOfTheUser, [mediaId])).toBeTruthy(); // Clean @@ -273,7 +275,7 @@ test('can add multiple media start nodes for a user', async ({umbracoApi, umbrac await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainMediaStartNodeIds(nameOfTheUser, [firstMediaId, secondMediaId])).toBeTruthy(); // Clean @@ -302,7 +304,7 @@ test('can remove a media start node from a user', async ({umbracoApi, umbracoUi} await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainMediaStartNodeIds(nameOfTheUser, [mediaId])).toBeFalsy(); // Clean @@ -321,7 +323,7 @@ test('can allow access to all documents for a user', async ({umbracoApi, umbraco await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.hasDocumentRootAccess).toBeTruthy() }); @@ -338,7 +340,7 @@ test('can allow access to all media for a user', async ({umbracoApi, umbracoUi}) await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.hasMediaRootAccess).toBeTruthy(); }); @@ -415,43 +417,55 @@ test('can change password for a user', {tag: '@smoke'}, async ({umbracoApi, umbr test('can disable a user', async ({umbracoApi, umbracoUi}) => { // Arrange const disabledStatus = 'Disabled'; + // We need to create a new user because the "TestUser" is used in other tests, which can affect if the user is disabled or not + const newTestUser = 'TestUserNumberTwo'; + await umbracoApi.user.ensureNameNotExists(newTestUser); const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName); - await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]); + await umbracoApi.user.createDefaultUser(newTestUser, newTestUser + userEmail, [userGroup.id]); await umbracoUi.user.goToUsers(); // Act - await umbracoUi.user.clickUserWithName(nameOfTheUser); + await umbracoUi.user.clickUserWithName(newTestUser); await umbracoUi.user.clickActionButton(); await umbracoUi.user.clickDisableButton(); await umbracoUi.user.clickConfirmDisableButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(newTestUser + NotificationConstantHelper.success.userDisabled); expect(umbracoUi.user.isUserDisabledTextVisible()).toBeTruthy(); - const userData = await umbracoApi.user.getByName(nameOfTheUser); + const userData = await umbracoApi.user.getByName(newTestUser); expect(userData.state).toBe(disabledStatus); + + // Clean + await umbracoApi.user.ensureNameNotExists(newTestUser); }); test('can enable a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange const inactiveStatus = 'Inactive'; + const newTestUser = 'TestUserNumberTwo'; + await umbracoApi.user.ensureNameNotExists(newTestUser); const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName); - const userId = await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]); + const userId = await umbracoApi.user.createDefaultUser(newTestUser, newTestUser + userEmail, [userGroup.id]); await umbracoApi.user.disable([userId]); await umbracoUi.user.goToUsers(); // Act - await umbracoUi.user.clickUserWithName(nameOfTheUser); + await umbracoUi.user.clickUserWithName(newTestUser); await umbracoUi.user.clickActionButton(); await umbracoUi.user.clickEnableButton(); await umbracoUi.user.clickConfirmEnableButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + // TODO: Unskip when it shows userEnabled/userInactive instead of userDisabled + // await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.userEnabled); await umbracoUi.user.isUserActiveTextVisible(); // The state of the user is not enabled. The reason for this is that the user has not logged in, resulting in the state Inactive. - const userData = await umbracoApi.user.getByName(nameOfTheUser); + const userData = await umbracoApi.user.getByName(newTestUser); expect(userData.state).toBe(inactiveStatus); + + // Clean + await umbracoApi.user.ensureNameNotExists(newTestUser); }); test('can add an avatar to a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { @@ -466,7 +480,7 @@ test('can add an avatar to a user', {tag: '@smoke'}, async ({umbracoApi, umbraco await umbracoUi.user.changePhotoWithFileChooser(filePath); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.avatarUploaded); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.avatarUrls).not.toHaveLength(0); }); @@ -483,7 +497,7 @@ test('can remove an avatar from a user', async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickRemovePhotoButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.avatarDeleted); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.avatarUrls).toHaveLength(0); }); @@ -507,11 +521,11 @@ test('can search for a user', async ({umbracoApi, umbracoUi}) => { // Arrange const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName); await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]); - const totalUsers = await umbracoApi.user.getUsersCount(); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToUsers(); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(totalUsers); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.searchInUserSection(nameOfTheUser); // Assert @@ -527,11 +541,11 @@ test('can filter by status', async ({umbracoApi, umbracoUi}) => { const inactiveStatus = 'Inactive'; const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName); await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]); - const totalUsers = await umbracoApi.user.getUsersCount(); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToUsers(); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(totalUsers); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.filterByStatusName(inactiveStatus); // Assert @@ -547,11 +561,11 @@ test('can filter by user groups', async ({umbracoApi, umbracoUi}) => { // Arrange const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName); await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]); - const totalUsers = await umbracoApi.user.getUsersCount(); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToUsers(); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(totalUsers); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.filterByGroupName(defaultUserGroupName); // Assert @@ -566,17 +580,18 @@ test('can order by newest user', async ({umbracoApi, umbracoUi}) => { // Arrange const userGroup = await umbracoApi.userGroup.getByName(defaultUserGroupName); await umbracoApi.user.createDefaultUser(nameOfTheUser, userEmail, [userGroup.id]); - const totalUsers = await umbracoApi.user.getUsersCount(); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToUsers(); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(totalUsers); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.orderByNewestUser(); // Assert // Wait for filtering to be done await umbracoUi.waitForTimeout(200); - await umbracoUi.user.doesUserSectionContainUserAmount(totalUsers); + + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.isUserWithNameTheFirstUserInList(nameOfTheUser); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts new file mode 100644 index 000000000000..59aeeffd73a2 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts @@ -0,0 +1,476 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const allPermissions = { + uiPermission: + ['Browse Node', + 'Create Document Blueprint', + 'Delete', + 'Create', + 'Notifications', + 'Publish', + 'Set permissions', + 'Unpublish', + 'Update', + 'Duplicate', + 'Move to', + 'Sort children', + 'Culture and Hostnames', + 'Public Access', + 'Rollback'], + verbPermission: [ + 'Umb.Document.Read', + 'Umb.Document.CreateBlueprint', + 'Umb.Document.Delete', + 'Umb.Document.Create', + 'Umb.Document.Notifications', + 'Umb.Document.Publish', + 'Umb.Document.Permissions', + 'Umb.Document.Unpublish', + 'Umb.Document.Update', + 'Umb.Document.Duplicate', + 'Umb.Document.Move', + 'Umb.Document.Sort', + 'Umb.Document.CultureAndHostnames', + 'Umb.Document.PublicAccess', + 'Umb.Document.Rollback' + ] +}; + +const englishLanguage = 'English (United States)'; + +const userGroupName = 'TestUserGroupName'; + +test.beforeEach(async ({umbracoUi, umbracoApi}) => { + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.users); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can create an empty user group', async ({page, umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.userGroup.clickUserGroupsButton(); + await page.pause(); + await umbracoUi.userGroup.clickCreateButton(); + await umbracoUi.userGroup.enterUserGroupName(userGroupName); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + expect(await umbracoApi.userGroup.doesNameExist(userGroupName)).toBeTruthy(); + // Checks if the user group was created in the UI as well + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.isUserGroupWithNameVisible(userGroupName); +}); + +test('can rename a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + const oldUserGroupName = 'OldUserGroupName'; + await umbracoApi.userGroup.ensureNameNotExists(oldUserGroupName); + await umbracoApi.userGroup.createEmptyUserGroup(oldUserGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(oldUserGroupName); + + // Act + await umbracoUi.userGroup.enterUserGroupName(userGroupName); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesNameExist(userGroupName)).toBeTruthy(); + // Checks if the user group was created in the UI as well + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.isUserGroupWithNameVisible(userGroupName); + await umbracoUi.userGroup.isUserGroupWithNameVisible(oldUserGroupName, false); +}); + +test('can update a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickPermissionsByName([allPermissions.uiPermission[0]]); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.reloadPage(); + await umbracoUi.userGroup.doesUserGroupHavePermission(allPermissions.uiPermission[0]); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.fallbackPermissions).toContain(allPermissions.verbPermission[0]); +}); + +test('can delete a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickActionsButton(); + await umbracoUi.userGroup.clickDeleteThreeDotsButton(); + await umbracoUi.userGroup.clickConfirmToDeleteButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); + expect(await umbracoApi.userGroup.doesNameExist(userGroupName)).toBeFalsy(); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.isUserGroupWithNameVisible(userGroupName, false); +}); + +test('can add a section to a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.addSectionWithNameToUserGroup('Content'); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Content'); +}) + +test('can add multiple sections to a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.addSectionWithNameToUserGroup('Media'); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Content'); + await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Media'); +}); + +test('can remove a section from a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickRemoveSectionFromUserGroup('Content'); + await umbracoUi.userGroup.clickConfirmRemoveButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.doesUserGroupHaveSection(userGroupName, 'Content', false); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.sections).toEqual([]); +}); + +test('can add a language to a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.addLanguageToUserGroup(englishLanguage); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.reloadPage(); + await umbracoUi.userGroup.doesUserGroupContainLanguage(englishLanguage); + expect(await umbracoApi.userGroup.doesUserGroupContainLanguage(userGroupName, 'en-US')).toBeTruthy(); +}) + +test('can enable all languages for a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickAllowAccessToAllLanguages(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainAccessToAllLanguages(userGroupName)).toBeTruthy(); +}) + +test('can add multiple languages to a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createUserGroupWithLanguage(userGroupName, 'en-US'); + const danishLanguage = 'Danish'; + await umbracoApi.language.ensureNameNotExists(danishLanguage); + await umbracoApi.language.createDanishLanguage(); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.addLanguageToUserGroup(danishLanguage); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.reloadPage(); + await umbracoUi.userGroup.doesUserGroupContainLanguage(englishLanguage); + await umbracoUi.userGroup.doesUserGroupContainLanguage(danishLanguage); + expect(await umbracoApi.userGroup.doesUserGroupContainLanguage(userGroupName, 'en-US')).toBeTruthy(); + expect(await umbracoApi.userGroup.doesUserGroupContainLanguage(userGroupName, 'da')).toBeTruthy(); + + // Clean + await umbracoApi.language.ensureNameNotExists(danishLanguage); +}) + +test('can remove language from a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createUserGroupWithLanguage(userGroupName, 'en-US'); + expect(await umbracoApi.userGroup.doesUserGroupContainLanguage(userGroupName, 'en-US')).toBeTruthy(); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickRemoveLanguageFromUserGroup(englishLanguage); + await umbracoUi.userGroup.clickConfirmRemoveButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.reloadPage(); + await umbracoUi.userGroup.doesUserGroupContainLanguage(englishLanguage, false); + expect(await umbracoApi.userGroup.doesUserGroupContainLanguage(userGroupName, 'en-US')).toBeFalsy(); +}) + +test('can add a content start node to a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + const documentTypeName = 'TestDocumentType'; + const documentName = 'TestDocument'; + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickChooseContentStartNodeButton(); + await umbracoUi.userGroup.clickLabelWithName(documentName); + await umbracoUi.userGroup.clickChooseContainerButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainContentStartNodeId(userGroupName, documentId)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can remove a content start node from a user group ', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeName = 'TestDocumentType'; + const documentName = 'TestDocument'; + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoApi.userGroup.createUserGroupWithDocumentStartNode(userGroupName, documentId); + expect(await umbracoApi.userGroup.doesUserGroupContainContentStartNodeId(userGroupName, documentId)).toBeTruthy(); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickRemoveContentStartNodeFromUserGroup(documentName); + await umbracoUi.userGroup.clickConfirmRemoveButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainContentStartNodeId(userGroupName, documentId)).toBeFalsy(); + + // Clean + await umbracoApi.document.ensureNameNotExists(documentTypeName); +}); + +test('can enable access to all content from a user group ', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickAllowAccessToAllDocuments(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainDocumentRootAccess(userGroupName)).toBeTruthy(); +}); + +test('can add a media start node to a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + const mediaName = 'TestMedia'; + await umbracoApi.media.ensureNameNotExists(mediaName); + const mediaId = await umbracoApi.media.createDefaultMediaFile(mediaName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickChooseMediaStartNodeButton(); + await umbracoUi.userGroup.clickMediaCardWithName(mediaName); + await umbracoUi.userGroup.clickSubmitButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainMediaStartNodeId(userGroupName, mediaId)).toBeTruthy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(mediaName); +}); + +test('can remove a media start node from a user group ', async ({umbracoApi, umbracoUi}) => { + // Arrange + const mediaName = 'TestMedia'; + await umbracoApi.media.ensureNameNotExists(mediaName); + const mediaId = await umbracoApi.media.createDefaultMediaFile(mediaName); + await umbracoApi.userGroup.createUserGroupWithMediaStartNode(userGroupName, mediaId); + expect(await umbracoApi.userGroup.doesUserGroupContainMediaStartNodeId(userGroupName, mediaId)).toBeTruthy(); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickRemoveMediaStartNodeFromUserGroup(mediaName); + await umbracoUi.userGroup.clickConfirmRemoveButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainMediaStartNodeId(userGroupName, mediaId)).toBeFalsy(); + + // Clean + await umbracoApi.media.ensureNameNotExists(mediaName); +}); + +test('can enable access to all media in a user group ', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickAllowAccessToAllMedia(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainMediaRootAccess(userGroupName)).toBeTruthy(); +}); + +test('can enable all permissions for a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + + // Act + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + await umbracoUi.userGroup.clickPermissionsByName(allPermissions.uiPermission); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.reloadPage(); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(allPermissions.uiPermission); + const userGroupData = await umbracoApi.userGroup.getByName(userGroupName); + expect(userGroupData.fallbackPermissions).toEqual(allPermissions.verbPermission); +}); + +test('can add granular permission to a specific document for a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeName = 'TestDocumentType'; + const documentName = 'TestDocument'; + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickAddGranularPermission(); + await umbracoUi.userGroup.clickLabelWithName(documentName); + await umbracoUi.userGroup.clickGranularPermissionsByName([allPermissions.uiPermission[0]]); + await umbracoUi.userGroup.clickConfirmButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainGranularPermissionsForDocument(userGroupName, documentId, [allPermissions.verbPermission[0]])).toBeTruthy(); + + // Clean + await umbracoApi.document.ensureNameNotExists(documentTypeName); +}); + +test('can add all granular permissions to a specific document for a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeName = 'TestDocumentType'; + const documentName = 'TestDocument'; + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoApi.userGroup.createEmptyUserGroup(userGroupName); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickAddGranularPermission(); + await umbracoUi.userGroup.clickLabelWithName(documentName); + await umbracoUi.userGroup.clickGranularPermissionsByName(allPermissions.uiPermission); + await umbracoUi.userGroup.clickConfirmButton(); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.reloadPage(); + await umbracoUi.userGroup.clickGranularPermissionWithName(documentName); + await umbracoUi.userGroup.doesUserGroupHavePermissionEnabled(allPermissions.uiPermission); + expect(await umbracoApi.userGroup.doesUserGroupContainGranularPermissionsForDocument(userGroupName, documentId, allPermissions.verbPermission)).toBeTruthy(); + + // Clean + await umbracoApi.document.ensureNameNotExists(documentTypeName); +}); + +test('can remove granular permission to a specific document for a user group', async ({umbracoApi, umbracoUi}) => { + // Arrange + const documentTypeName = 'TestDocumentType'; + const documentName = 'TestDocument'; + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + const documentId = await umbracoApi.document.createDefaultDocument(documentName, documentTypeId); + await umbracoApi.userGroup.createUserGroupWithPermissionsForSpecificDocumentWithBrowseNode(userGroupName, documentId); + expect(await umbracoApi.userGroup.doesUserGroupContainGranularPermissionsForDocument(userGroupName, documentId, [allPermissions.verbPermission[0]])).toBeTruthy(); + await umbracoUi.userGroup.clickUserGroupsButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickRemoveGranularPermissionWithName(documentName); + await umbracoUi.userGroup.clickSaveButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + expect(await umbracoApi.userGroup.doesUserGroupContainGranularPermissionsForDocument(userGroupName, documentId, [allPermissions.verbPermission[0]])).toBeFalsy(); + + // Clean + await umbracoApi.document.ensureNameNotExists(documentTypeName); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts index 93c8c20c43fb..3ab590503dbb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/auth.setup.ts @@ -6,7 +6,7 @@ setup('authenticate', async ({page}) => { const umbracoUi = new UiHelpers(page); await umbracoUi.goToBackOffice(); - await page.waitForTimeout(10000); + await page.waitForTimeout(5000); await umbracoUi.login.enterEmail(process.env.UMBRACO_USER_LOGIN); await umbracoUi.login.enterPassword(process.env.UMBRACO_USER_PASSWORD); await umbracoUi.login.clickLoginButton(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/StandardMvc-Package.xml b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/StandardMvc-Package.xml index 5bba7cfa4ec5..f3780e1df80f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/StandardMvc-Package.xml +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/Importing/StandardMvc-Package.xml @@ -927,7 +927,7 @@ /* This is your basic query to select the nodes you want */ - var nodes = Model.Content.Children.Where(x => x.DocumentTypeAlias == "NewsArticle").OrderBy(x=>x.CreateDate); + var nodes = Model.Content.Children().Where(x => x.DocumentTypeAlias == "NewsArticle").OrderBy(x=>x.CreateDate); int totalNodes = nodes.Count(); int totalPages = (int)Math.Ceiling((double)totalNodes / (double)pageSize); @@ -1023,7 +1023,7 @@ x.IsVisible() && x.TemplateId > 0 && Umbraco.MemberHasAccess(x.Id, x.Path)); + var pages = Model.Content.Children().Where(x => x.IsVisible() && x.TemplateId > 0 && Umbraco.MemberHasAccess(x.Id, x.Path)); }
    @@ -1347,7 +1347,7 @@ @helper traverse(IPublishedContent node) { - var cc = node.Children.Where(x=>x.IsVisible() && x.TemplateId > 0); + var cc = node.Children().Where(x=>x.IsVisible() && x.TemplateId > 0); if (cc.Count()>0) {