From b2a2df7a8a0979a54bc2297b1346b6a99a1dc495 Mon Sep 17 00:00:00 2001 From: Jason Elkin Date: Thu, 22 Aug 2024 21:01:21 +0100 Subject: [PATCH 01/95] Ignore Visual Studio's generated launchSettings file. --- tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore diff --git a/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore new file mode 100644 index 0000000000..ff99115ae5 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest.UmbracoProject/.gitignore @@ -0,0 +1,2 @@ +# Ignore Visual Studio's generated launchSettings file. +Properties/launchSettings.json From a47a1775f22be137cb6cffe400f911d8202acf35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nick=20Ho=C3=A0ng?= <4602123+nick-hoang@users.noreply.github.com> Date: Tue, 3 Sep 2024 06:54:32 +0700 Subject: [PATCH 02/95] Prevent templates being editable when using Production runtime mode (#16923) Co-authored-by: Nick Hoang Co-authored-by: Jason Elkin --- src/Umbraco.Web.UI.Client/src/views/templates/edit.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index a5527095d7..76bb769c9e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -127,6 +127,7 @@ type="submit" button-style="success" state="vm.page.saveButtonState" + disabled="vm.runtimeModeProduction" shortcut="ctrl+s" label="Save" label-key="buttons_save"> From 3e6116fcbafebeaa4fed3a209855cc6bb717cfa6 Mon Sep 17 00:00:00 2001 From: Miguel Pinto Date: Thu, 5 Sep 2024 14:08:48 +0200 Subject: [PATCH 03/95] No longer shows success message if content moving is cancelled (#15051) * Fix for issue https://github.com/umbraco/Umbraco-CMS/issues/13923 - Added AttemptMove method to the ContentService - Updated ContentController PostMove method to return ValidationProblem whenever the node is not moved * Align changes with V14 solution. Make it non breaking. --------- Co-authored-by: Laura Neto <12862535+lauraneto@users.noreply.github.com> --- src/Umbraco.Core/Services/ContentService.cs | 20 ++++++++++++------- src/Umbraco.Core/Services/IContentService.cs | 16 +++++++++++++++ .../Controllers/ContentController.cs | 7 ++++++- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index e723bfcdd2..8bdaba271e 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2448,22 +2448,26 @@ public class ContentService : RepositoryService, IContentService /// The to move /// Id of the Content's new Parent /// Optional Id of the User moving the Content - public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) + public void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId) => + AttemptMove(content, parentId, userId); + + /// + [Obsolete("Adds return type to Move method. Will be removed in V14, as the original method will be adjusted.")] + public OperationResult AttemptMove(IContent content, int parentId, int userId = Constants.Security.SuperUserId) { + EventMessages eventMessages = EventMessagesFactory.Get(); + if (content.ParentId == parentId) { - return; + return OperationResult.Succeed(eventMessages); } // if moving to the recycle bin then use the proper method if (parentId == Constants.System.RecycleBinContent) { - MoveToRecycleBin(content, userId); - return; + return MoveToRecycleBin(content, userId); } - EventMessages eventMessages = EventMessagesFactory.Get(); - var moves = new List<(IContent, string)>(); using (ICoreScope scope = ScopeProvider.CreateCoreScope()) @@ -2482,7 +2486,7 @@ public class ContentService : RepositoryService, IContentService if (scope.Notifications.PublishCancelable(movingNotification)) { scope.Complete(); - return; // causes rollback + return OperationResult.Cancel(eventMessages);// causes rollback } // if content was trashed, and since we're not moving to the recycle bin, @@ -2517,6 +2521,8 @@ public class ContentService : RepositoryService, IContentService scope.Complete(); } + + return OperationResult.Succeed(eventMessages); } // MUST be called from within WriteLock diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 0d3cc80b82..1733a74142 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -1,3 +1,4 @@ +using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Persistence.Querying; @@ -315,6 +316,21 @@ public interface IContentService : IContentServiceBase /// void Move(IContent content, int parentId, int userId = Constants.Security.SuperUserId); + /// + /// Attempts to move the to under the node with id . + /// + /// The that shall be moved. + /// The id of the new parent node. + /// Id of the user attempting to move . + /// Success if moving succeeded, otherwise Failed. + [Obsolete("Adds return type to Move method. Will be removed in V14, as the original method will be adjusted.")] + OperationResult + AttemptMove(IContent content, int parentId, int userId = Constants.Security.SuperUserId) + { + Move(content, parentId, userId); + return OperationResult.Succeed(new EventMessages()); + } + /// /// Copies a document. /// diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 81b37bb108..62cbae3012 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -2254,7 +2254,12 @@ public class ContentController : ContentControllerBase return null; } - _contentService.Move(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + OperationResult moveResult = _contentService.AttemptMove(toMove, move.ParentId, _backofficeSecurityAccessor.BackOfficeSecurity?.GetUserId().Result ?? -1); + + if (!moveResult.Success) + { + return ValidationProblem(); + } return Content(toMove.Path, MediaTypeNames.Text.Plain, Encoding.UTF8); } From 3667217053b9643d6369fe360ed7f012c12ffdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 17 Sep 2024 09:51:47 +0200 Subject: [PATCH 04/95] only resolve if value is present (#17070) --- .../src/common/services/rte-blockeditor-clipboard.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/rte-blockeditor-clipboard.service.js b/src/Umbraco.Web.UI.Client/src/common/services/rte-blockeditor-clipboard.service.js index 216b7fe1e8..0a1aee1f27 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/rte-blockeditor-clipboard.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/rte-blockeditor-clipboard.service.js @@ -50,13 +50,13 @@ function rawRteBlockResolver(propertyValue, propPasteResolverMethod) { - if (propertyValue != null && typeof propertyValue === "object") { + if (propertyValue && typeof propertyValue === "object" && propertyValue.markup) { // object property of 'blocks' holds the data for the Block Editor. var value = propertyValue.blocks; // we got an object, and it has these three props then we are most likely dealing with a Block Editor. - if ((value.layout !== undefined && value.contentData !== undefined && value.settingsData !== undefined)) { + if ((value && value.layout !== undefined && value.contentData !== undefined && value.settingsData !== undefined)) { // replaceUdisOfObject replaces udis of the value object(by instance reference), but also returns the updated markup (as we cant update the reference of a string). propertyValue.markup = replaceUdisOfObject(value.layout, value, propertyValue.markup); From d58768f26d1db7bf6b6a4628c10f7aa61012ad94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 17 Sep 2024 09:52:00 +0200 Subject: [PATCH 05/95] utilize lock unlock events for readonly mode while saving (#17077) --- .../components/content/edit.controller.js | 7 +++++-- .../content/umbtabbedcontent.directive.js | 13 ++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 8a8ae0a36b..e3b3ba413c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -5,7 +5,7 @@ appState, contentResource, entityResource, navigationService, notificationsService, contentAppHelper, serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper, editorState, $http, eventsService, overlayService, $location, localStorageService, treeService, - $exceptionHandler, uploadTracker) { + $exceptionHandler, uploadTracker) { var evts = []; var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode; @@ -497,6 +497,7 @@ //Set them all to be invalid var fieldsToRollback = checkValidility(); eventsService.emit("content.saving", { content: $scope.content, action: args.action }); + eventsService.emit("form.lock"); return contentEditingHelper.contentEditorPerformSave({ saveMethod: args.saveMethod, @@ -517,6 +518,7 @@ syncTreeNode($scope.content, data.path, false, args.reloadChildren); eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: true }); + eventsService.emit("form.unlock"); if($scope.contentForm.$invalid !== true) { resetNestedFieldValiation(fieldsToRollback); @@ -534,6 +536,7 @@ if (err && err.status === 400 && err.data) { // content was saved but is invalid. eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: false }); + eventsService.emit("form.unlock"); } return $q.reject(err); @@ -1002,7 +1005,7 @@ const openPreviewWindow = (url, target) => { // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. - + const previewWindow = $window.open(url, target); previewWindow.addEventListener('load', () => { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js index f003e1afa6..e76da32a54 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbtabbedcontent.directive.js @@ -2,7 +2,7 @@ 'use strict'; /** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */ - function tabbedContentDirective($timeout, $filter, contentEditingHelper, contentTypeHelper) { + function tabbedContentDirective($timeout, $filter, contentEditingHelper, contentTypeHelper, eventsService) { function link($scope, $element) { @@ -156,14 +156,13 @@ } }); - $scope.$on("formSubmitting", function() { - $scope.allowUpdate = false; + eventsService.on("form.lock", function() { + $scope.$evalAsync(() => { + $scope.allowUpdate = false; + }); }); - $scope.$on("formSubmitted", function() { - setAllowUpdate(); - }); - $scope.$on("formSubmittedValidationFailed", function() { + eventsService.on("form.unlock", function() { setAllowUpdate(); }); From eb0f8b5c24f0e5d1dbdfc98069a874def891c833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 17 Sep 2024 10:06:16 +0200 Subject: [PATCH 06/95] fire change event (#17078) --- .../src/common/services/tinymce.service.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 18707c1f60..7ed6b9ee50 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -1056,6 +1056,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s editor.undoManager.clear(); } } + + angularHelper.safeApply($rootScope, function () { + editor.dispatch("Change"); + }); }); }); From d64bf5de2207798b58609f2cbe2c4a06a3b7761c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20=C3=96hman?= Date: Tue, 17 Sep 2024 13:56:58 +0200 Subject: [PATCH 07/95] v13.5 - New Swedish translation crashes Umbraco, removed duplicate areas. (#17059) --- .../EmbeddedResources/Lang/sv.xml | 3 --- .../Services/LocalizedTextService.cs | 25 +++++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml index 0a688083eb..47a787aada 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml @@ -4,9 +4,6 @@ The Umbraco community https://docs.umbraco.com/umbraco-cms/extending/language-files - - Innehåll - Hantera domännamn Hantera versioner diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs index 1634f60baa..51012200bd 100644 --- a/src/Umbraco.Core/Services/LocalizedTextService.cs +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -350,7 +350,13 @@ public class LocalizedTextService : ILocalizedTextService IEnumerable areas = xmlSource[cult].Value.XPathSelectElements("//area"); foreach (XElement area in areas) { - var result = new Dictionary(StringComparer.InvariantCulture); + var areaAlias = area.Attribute("alias")!.Value; + + if (!overallResult.TryGetValue(areaAlias, out IDictionary? result)) + { + result = new Dictionary(StringComparer.InvariantCulture); + } + IEnumerable keys = area.XPathSelectElements("./key"); foreach (XElement key in keys) { @@ -364,7 +370,10 @@ public class LocalizedTextService : ILocalizedTextService } } - overallResult.Add(area.Attribute("alias")!.Value, result); + if (!overallResult.ContainsKey(areaAlias)) + { + overallResult.Add(areaAlias, result); + } } // Merge English Dictionary @@ -374,11 +383,11 @@ public class LocalizedTextService : ILocalizedTextService IEnumerable enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area"); foreach (XElement area in enUS) { - IDictionary - result = new Dictionary(StringComparer.InvariantCulture); - if (overallResult.ContainsKey(area.Attribute("alias")!.Value)) + var areaAlias = area.Attribute("alias")!.Value; + + if (!overallResult.TryGetValue(areaAlias, out IDictionary? result)) { - result = overallResult[area.Attribute("alias")!.Value]; + result = new Dictionary(StringComparer.InvariantCulture); } IEnumerable keys = area.XPathSelectElements("./key"); @@ -394,9 +403,9 @@ public class LocalizedTextService : ILocalizedTextService } } - if (!overallResult.ContainsKey(area.Attribute("alias")!.Value)) + if (!overallResult.ContainsKey(areaAlias)) { - overallResult.Add(area.Attribute("alias")!.Value, result); + overallResult.Add(areaAlias, result); } } } From 6a453a091060479960ddca786b748184a7ad6d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20=C3=96hman?= Date: Tue, 17 Sep 2024 13:56:58 +0200 Subject: [PATCH 08/95] v13.5 - New Swedish translation crashes Umbraco, removed duplicate areas. (#17059) Cherry-picked from d64bf5de2207798b58609f2cbe2c4a06a3b7761c --- .../EmbeddedResources/Lang/sv.xml | 3 --- .../Services/LocalizedTextService.cs | 25 +++++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml index 0a688083eb..47a787aada 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml @@ -4,9 +4,6 @@ The Umbraco community https://docs.umbraco.com/umbraco-cms/extending/language-files - - Innehåll - Hantera domännamn Hantera versioner diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs index 1634f60baa..51012200bd 100644 --- a/src/Umbraco.Core/Services/LocalizedTextService.cs +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -350,7 +350,13 @@ public class LocalizedTextService : ILocalizedTextService IEnumerable areas = xmlSource[cult].Value.XPathSelectElements("//area"); foreach (XElement area in areas) { - var result = new Dictionary(StringComparer.InvariantCulture); + var areaAlias = area.Attribute("alias")!.Value; + + if (!overallResult.TryGetValue(areaAlias, out IDictionary? result)) + { + result = new Dictionary(StringComparer.InvariantCulture); + } + IEnumerable keys = area.XPathSelectElements("./key"); foreach (XElement key in keys) { @@ -364,7 +370,10 @@ public class LocalizedTextService : ILocalizedTextService } } - overallResult.Add(area.Attribute("alias")!.Value, result); + if (!overallResult.ContainsKey(areaAlias)) + { + overallResult.Add(areaAlias, result); + } } // Merge English Dictionary @@ -374,11 +383,11 @@ public class LocalizedTextService : ILocalizedTextService IEnumerable enUS = xmlSource[englishCulture].Value.XPathSelectElements("//area"); foreach (XElement area in enUS) { - IDictionary - result = new Dictionary(StringComparer.InvariantCulture); - if (overallResult.ContainsKey(area.Attribute("alias")!.Value)) + var areaAlias = area.Attribute("alias")!.Value; + + if (!overallResult.TryGetValue(areaAlias, out IDictionary? result)) { - result = overallResult[area.Attribute("alias")!.Value]; + result = new Dictionary(StringComparer.InvariantCulture); } IEnumerable keys = area.XPathSelectElements("./key"); @@ -394,9 +403,9 @@ public class LocalizedTextService : ILocalizedTextService } } - if (!overallResult.ContainsKey(area.Attribute("alias")!.Value)) + if (!overallResult.ContainsKey(areaAlias)) { - overallResult.Add(area.Attribute("alias")!.Value, result); + overallResult.Add(areaAlias, result); } } } From 842cacde1923beaa90b373dab6844c6a009f8bd7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 17 Sep 2024 14:50:57 +0200 Subject: [PATCH 09/95] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index ff1a0edbe5..cb406a4d22 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.5.0", + "version": "13.5.1", "assemblyVersion": { "precision": "build" }, From 6939472f37c94d070ba8b8622293f9ac10c83d20 Mon Sep 17 00:00:00 2001 From: Terence Burridge Date: Wed, 20 Mar 2024 17:58:48 +0000 Subject: [PATCH 10/95] Update valid reasons not to have a template on a content node to include having a redirect field Cherry-picked from: 385a5345b1dcd1fbc20afb854cfeff1c36f52210 --- src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs index d8fbca45d4..c46bb890b1 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -128,11 +128,12 @@ public class UmbracoRouteValuesFactory : IUmbracoRouteValuesFactory IPublishedRequest request = def.PublishedRequest; // Here we need to check if there is no hijacked route and no template assigned but there is a content item. - // If this is the case we want to return a blank page. + // If this is the case we want to return a blank page, the only exception being if the content item has a redirect field present. // We also check if templates have been disabled since if they are then we're allowed to render even though there's no template, // for example for json rendering in headless. if (request.HasPublishedContent() && !request.HasTemplate() + && !request.IsRedirect() && !_umbracoFeatures.Disabled.DisableTemplates && !hasHijackedRoute) { From 9b19d63a6a4ea44bd4b56bbb0874284a1bc5ba52 Mon Sep 17 00:00:00 2001 From: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:19:09 +0700 Subject: [PATCH 11/95] update ImageSharpMiddlewareOption for fixing invalid width and height (#17126) Co-authored-by: Lan Nguyen Thuy --- .../ConfigureImageSharpMiddlewareOptions.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs index 9a1ecead89..1ef672270e 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Headers; using Microsoft.Extensions.Options; @@ -48,16 +49,26 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); - if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) + if (context.Commands.Contains(ResizeWebProcessor.Width)) { - context.Commands.Remove(ResizeWebProcessor.Width); + if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), NumberStyles.Integer, + CultureInfo.InvariantCulture, out var width) + || width < 0 + || width >= _imagingSettings.Resize.MaxWidth) + { + context.Commands.Remove(ResizeWebProcessor.Width); + } } - int height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); - if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) + if (context.Commands.Contains(ResizeWebProcessor.Height)) { - context.Commands.Remove(ResizeWebProcessor.Height); + if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), NumberStyles.Integer, + CultureInfo.InvariantCulture, out var height) + || height < 0 + || height >= _imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Height); + } } return Task.CompletedTask; From a40eadcfcee4413ed2d7fcc0928f615ea9aabddc Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 26 Sep 2024 07:52:39 +0200 Subject: [PATCH 12/95] Add `RemoveDefault()` extension method to fluent API for CMS webhook events (#15424) * Add RemoveDefault extension method * Move default webhook event types to single list (cherry picked from commit 8f26263178656f092972e845e332962e9e158f1e) --- ...hookEventCollectionBuilderCmsExtensions.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs index 361891de6a..679e105b72 100644 --- a/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs +++ b/src/Umbraco.Core/Webhooks/WebhookEventCollectionBuilderCmsExtensions.cs @@ -9,6 +9,15 @@ namespace Umbraco.Cms.Core.DependencyInjection; /// public static class WebhookEventCollectionBuilderCmsExtensions { + private static readonly Type[] _defaultTypes = + [ + typeof(ContentDeletedWebhookEvent), + typeof(ContentPublishedWebhookEvent), + typeof(ContentUnpublishedWebhookEvent), + typeof(MediaDeletedWebhookEvent), + typeof(MediaSavedWebhookEvent), + ]; + /// /// Adds the default webhook events. /// @@ -21,12 +30,24 @@ public static class WebhookEventCollectionBuilderCmsExtensions /// public static WebhookEventCollectionBuilderCms AddDefault(this WebhookEventCollectionBuilderCms builder) { - builder.Builder - .Add() - .Add() - .Add() - .Add() - .Add(); + builder.Builder.Add(_defaultTypes); + + return builder; + } + + /// + /// Removes the default webhook events. + /// + /// The builder. + /// + /// The builder. + /// + public static WebhookEventCollectionBuilderCms RemoveDefault(this WebhookEventCollectionBuilderCms builder) + { + foreach (Type type in _defaultTypes) + { + builder.Builder.Remove(type); + } return builder; } From f1cddd91c6e85e4ed454fa42578eb98ca1728aef Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 26 Sep 2024 09:54:43 +0200 Subject: [PATCH 13/95] Missing context complete (cherry picked from commit fdb9cfa3e7ada8db6effb6ce7cb9a58f09817538) --- .../Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs index 67bdaf7395..461ed59c8e 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_14_0_0/AddGuidsToUserGroups.cs @@ -23,6 +23,7 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase // If the new column already exists we'll do nothing. if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) { + Context.Complete(); return; } @@ -31,10 +32,12 @@ public class AddGuidsToUserGroups : UnscopedMigrationBase if (DatabaseType != DatabaseType.SQLite) { MigrateSqlServer(); + Context.Complete(); return; } MigrateSqlite(); + Context.Complete(); } private void MigrateSqlServer() From c9c9374de1150019f6d484b74038742edd1d1e93 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:39:12 +0200 Subject: [PATCH 14/95] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index a5500fd8de..b2c598f6ef 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit a5500fd8de2fb14285d8f99cd3d5edeb1c5eb462 +Subproject commit b2c598f6ef0b62bb64186c61125f4d00177b48ca From 45f43a6b7ac632a76d4bd0f70176c37b69cdbf6c Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Fri, 27 Sep 2024 10:36:15 +0700 Subject: [PATCH 15/95] V14 Added Content tests with different document types properties (#17131) * Added tests for Allow At Root property * Added Content tests for Allowed Child Nodes property * Added Content tests for the Allow at root property * Added Content tests for the Allowed child node property * Added Content tests for the Collection property * Added Content tests with allow vary by culture * Added more waits * Updated tests due to api helper changes * Added Content tests with allowed templates * Bumped version of test helper * Updated code due to api helper changes * Fixed naming --- .../package-lock.json | 9 +- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../ContentWithAllowAtRoot.spec.ts | 27 ++++ .../ContentWithAllowVaryByCulture.spec.ts | 128 ++++++++++++++++ .../ContentWithAllowedChildNodes.spec.ts | 98 +++++++++++++ .../ContentWithAllowedTemplates.spec.ts | 66 +++++++++ .../ContentWithCollections.spec.ts | 138 ++++++++++++++++++ .../DocumentTypeDesignTab.spec.ts | 2 +- 8 files changed, 463 insertions(+), 7 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index bb227d8a12..99eeb24f0a 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.20", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.84", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.86", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -64,10 +64,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.84", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.84.tgz", - "integrity": "sha512-vH13Lg48knTkkLVTwhMXUKTOdjtmixFj0wF5Qhgb++13u4AVDb+oW+TbFwTjSYaLeNMraq5Uhwmto/XuJPs2Rw==", - "license": "MIT", + "version": "2.0.0-beta.86", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.86.tgz", + "integrity": "sha512-tF7nJCMgBJwaPtxWAuDOJ9lc3T11aO6ped9AxzAJTmzFdSJG16w8jzjWiNgCaU2xRsw5fRyos+I1YrFW249vLw==", "dependencies": { "@umbraco/json-models-builders": "2.0.20", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 30706ad5de..a6949b54fa 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.20", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.84", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.86", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" 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 0000000000..f028220533 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts @@ -0,0 +1,27 @@ +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 + const noAllowedDocumentTypeAvailableMessage = 'There are no allowed Document Types available for creating content here'; + 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); + await umbracoUi.content.doesModalHaveText(noAllowedDocumentTypeAvailableMessage); +}); \ No newline at end of file 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 0000000000..1c9fbc542f --- /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 0000000000..2016a0ee4c --- /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 0000000000..f58d86c36e --- /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 0000000000..837f7ababa --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts @@ -0,0 +1,138 @@ +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 + const noItemsToShowMessage = 'There are no items to show in the list.'; + 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.doesDocumentWorkspaceHaveText(noItemsToShowMessage); + 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/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts index eb87b7288a..3f19d68085 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts @@ -397,7 +397,7 @@ test('can enable validation for a property in a document type', async ({umbracoA test('can allow vary by culture for a property in a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); - await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, true); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, groupName, false); await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); // Act From 762d72b0184c44e739d8d544d30aa1396a7eb8e3 Mon Sep 17 00:00:00 2001 From: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com> Date: Wed, 25 Sep 2024 18:19:09 +0700 Subject: [PATCH 16/95] update ImageSharpMiddlewareOption for fixing invalid width and height (#17126) Co-authored-by: Lan Nguyen Thuy (cherry picked from commit 9b19d63a6a4ea44bd4b56bbb0874284a1bc5ba52) --- .../ConfigureImageSharpMiddlewareOptions.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs index 9a1ecead89..1ef672270e 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Headers; using Microsoft.Extensions.Options; @@ -48,16 +49,26 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); - if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) + if (context.Commands.Contains(ResizeWebProcessor.Width)) { - context.Commands.Remove(ResizeWebProcessor.Width); + if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), NumberStyles.Integer, + CultureInfo.InvariantCulture, out var width) + || width < 0 + || width >= _imagingSettings.Resize.MaxWidth) + { + context.Commands.Remove(ResizeWebProcessor.Width); + } } - int height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); - if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) + if (context.Commands.Contains(ResizeWebProcessor.Height)) { - context.Commands.Remove(ResizeWebProcessor.Height); + if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), NumberStyles.Integer, + CultureInfo.InvariantCulture, out var height) + || height < 0 + || height >= _imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Height); + } } return Task.CompletedTask; From 9a12eea495c4122b941bd21c5afd7726cf87d920 Mon Sep 17 00:00:00 2001 From: NguyenThuyLan <116753400+NguyenThuyLan@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:41:55 +0700 Subject: [PATCH 17/95] Fix error format code (#17146) * update ImageSharpMiddlewareOption for fixing invalid width and height (#17126) Co-authored-by: Lan Nguyen Thuy * Fix issue format parameters --------- Co-authored-by: Lan Nguyen Thuy --- .../ConfigureImageSharpMiddlewareOptions.cs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs index 9a1ecead89..79fcd0a9bf 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Headers; using Microsoft.Extensions.Options; @@ -48,16 +49,32 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), context.Culture); - if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) + if (context.Commands.Contains(ResizeWebProcessor.Width)) { - context.Commands.Remove(ResizeWebProcessor.Width); + if (!int.TryParse( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var width) + || width < 0 + || width >= _imagingSettings.Resize.MaxWidth) + { + context.Commands.Remove(ResizeWebProcessor.Width); + } } - int height = context.Parser.ParseValue(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), context.Culture); - if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) + if (context.Commands.Contains(ResizeWebProcessor.Height)) { - context.Commands.Remove(ResizeWebProcessor.Height); + if (!int.TryParse( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var height) + || height < 0 + || height >= _imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Height); + } } return Task.CompletedTask; From 46604909be4a3ab9f5e9a1ed32542fbe45e074ee Mon Sep 17 00:00:00 2001 From: NguyenThuyLan <116753400+nguyenthuylan@users.noreply.github.com> Date: Fri, 27 Sep 2024 07:41:55 +0200 Subject: [PATCH 18/95] Fix error format code (#17146) * update ImageSharpMiddlewareOption for fixing invalid width and height (#17126) Co-authored-by: Lan Nguyen Thuy * Fix issue format parameters --------- Co-authored-by: Lan Nguyen Thuy (cherry picked from commit 9a12eea495c4122b941bd21c5afd7726cf87d920) --- .../ConfigureImageSharpMiddlewareOptions.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs index 1ef672270e..79fcd0a9bf 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs @@ -51,8 +51,11 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions= _imagingSettings.Resize.MaxWidth) { @@ -62,8 +65,11 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions= _imagingSettings.Resize.MaxHeight) { From d57d12d54d95d1a4df22553692798973f90f29d7 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Fri, 27 Sep 2024 07:43:12 +0200 Subject: [PATCH 19/95] Fixed imagesharp 2 also --- .../ConfigureImageSharpMiddlewareOptions.cs | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Cms.Imaging.ImageSharp2/ConfigureImageSharpMiddlewareOptions.cs b/src/Umbraco.Cms.Imaging.ImageSharp2/ConfigureImageSharpMiddlewareOptions.cs index 8daa1b689b..dcc67bf5d3 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp2/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp2/ConfigureImageSharpMiddlewareOptions.cs @@ -1,3 +1,4 @@ +using System.Globalization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Headers; using Microsoft.Extensions.Options; @@ -47,20 +48,32 @@ public sealed class ConfigureImageSharpMiddlewareOptions : IConfigureOptions( - context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), - context.Culture); - if (width <= 0 || width > _imagingSettings.Resize.MaxWidth) + if (context.Commands.Contains(ResizeWebProcessor.Width)) { - context.Commands.Remove(ResizeWebProcessor.Width); + if (!int.TryParse( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var width) + || width < 0 + || width >= _imagingSettings.Resize.MaxWidth) + { + context.Commands.Remove(ResizeWebProcessor.Width); + } } - var height = context.Parser.ParseValue( - context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), - context.Culture); - if (height <= 0 || height > _imagingSettings.Resize.MaxHeight) + if (context.Commands.Contains(ResizeWebProcessor.Height)) { - context.Commands.Remove(ResizeWebProcessor.Height); + if (!int.TryParse( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var height) + || height < 0 + || height >= _imagingSettings.Resize.MaxHeight) + { + context.Commands.Remove(ResizeWebProcessor.Height); + } } return Task.CompletedTask; From 4fae91d55c544f912973926f80a8ba5b49ccd751 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:32:44 +0200 Subject: [PATCH 20/95] V14 QA added content tests with list view content (#17115) * Added tests for List view * More tests * Added rest of test * Bumped version * Fixed failing tests * Added tests and fixed comments * Cleaned up tests * Bumped testhelpers * Bumped again * Set condition to only run sql server when enabled * Run all content test * Reverted changes --- build/azure-pipelines.yml | 5 + .../ContentWithListViewContent.spec.ts | 393 ++++++++++++++++++ .../tests/DefaultConfig/Media/Media.spec.ts | 13 +- 3 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithListViewContent.spec.ts diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 9e7207bf07..faa31ae511 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: sqlServerAcceptanceTests + displayName: Run SQL Server Acceptance Tests + type: boolean + default: false - name: myGetDeploy displayName: Deploy to MyGet type: boolean @@ -549,6 +553,7 @@ stages: - job: displayName: E2E Tests (SQL Server) + condition: eq(${{parameters.sqlServerAcceptanceTests}}, True) variables: # Connection string CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True 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 0000000000..0a39d0c4f9 --- /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(); + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.doesListViewHaveNoItemsInList(); + + // Assert + 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/Media/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index 8dcf57923a..803eb63c79 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -192,7 +192,7 @@ test('can trash a media item', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickConfirmTrashButton(); // Assert - await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeTruthy(); @@ -212,7 +212,8 @@ test('can restore a media item from the recycle bin', async ({umbracoApi, umbrac await umbracoUi.media.restoreMediaItem(mediaFileName); // Assert - await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.reloadMediaTree(); await umbracoUi.media.isTreeItemVisible(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); @@ -229,11 +230,11 @@ test('can delete a media item from the recycle bin', async ({umbracoApi, umbraco await umbracoUi.media.goToSection(ConstantHelper.sections.media); // Act - await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName); await umbracoUi.media.deleteMediaItem(mediaFileName); // Assert - await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); }); @@ -246,12 +247,12 @@ test('can empty the recycle bin', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.goToSection(ConstantHelper.sections.media); // Act - await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName); await umbracoUi.media.clickEmptyRecycleBinButton(); await umbracoUi.media.clickConfirmEmptyRecycleBinButton(); // Assert - await umbracoUi.media.isMediaItemVisibleInRecycleBin(mediaFileName, false); + await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); expect(await umbracoApi.media.doesMediaItemExistInRecycleBin(mediaFileName)).toBeFalsy(); }); From 2d7c00f27f629c8b24cd37aa8940892bb593ed18 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:24:03 +0200 Subject: [PATCH 21/95] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index b2c598f6ef..71eeca0963 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit b2c598f6ef0b62bb64186c61125f4d00177b48ca +Subproject commit 71eeca096330781e1a4f061aecaab528009234c5 From 348f1f2baee858c7a4fae7ac769eefa72b231272 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 1 Oct 2024 07:58:41 +0200 Subject: [PATCH 22/95] Change webhook permissions to require webhook tree access for all endpoints --- .../Controllers/Webhook/CreateWebhookController.cs | 3 --- .../Controllers/Webhook/DeleteWebhookController.cs | 3 --- .../Controllers/Webhook/UpdateWebhookController.cs | 3 --- .../Controllers/Webhook/WebhookControllerBase.cs | 3 +++ 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/CreateWebhookController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/CreateWebhookController.cs index c513c83d70..705292c82a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/CreateWebhookController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/CreateWebhookController.cs @@ -1,5 +1,4 @@ using Asp.Versioning; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -8,12 +7,10 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Webhook; [ApiVersion("1.0")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)] public class CreateWebhookController : WebhookControllerBase { private readonly IWebhookService _webhookService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/DeleteWebhookController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/DeleteWebhookController.cs index ecb8d7d9f7..a45302464a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/DeleteWebhookController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/DeleteWebhookController.cs @@ -1,5 +1,4 @@ using Asp.Versioning; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; @@ -7,12 +6,10 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Webhook; [ApiVersion("1.0")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)] public class DeleteWebhookController : WebhookControllerBase { private readonly IWebhookService _webhookService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/UpdateWebhookController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/UpdateWebhookController.cs index c5469d575f..8f22721b5e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/UpdateWebhookController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/UpdateWebhookController.cs @@ -1,5 +1,4 @@ using Asp.Versioning; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -8,12 +7,10 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; -using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Webhook; [ApiVersion("1.0")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)] public class UpdateWebhookController : WebhookControllerBase { private readonly IWebhookService _webhookService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/WebhookControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/WebhookControllerBase.cs index 98c3868c65..d32de4bf7a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Webhook/WebhookControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Webhook/WebhookControllerBase.cs @@ -1,13 +1,16 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Common.Builders; using Umbraco.Cms.Api.Management.Routing; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Webhook; [VersionedApiBackOfficeRoute("webhook")] [ApiExplorerSettings(GroupName = "Webhook")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessWebhooks)] public abstract class WebhookControllerBase : ManagementApiControllerBase { protected IActionResult WebhookOperationStatusResult(WebhookOperationStatus status) => From 865787db1d51477d3f191ce0696737ffcb2b2953 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:48:41 +0200 Subject: [PATCH 23/95] Bump version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 3b2f02bc66..05117000e8 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "14.3.0", + "version": "14.3.1", "assemblyVersion": { "precision": "build" }, From 9bab74d30ef17aad03a6c7d3d89eedc4b1b4999c Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:55:34 +0200 Subject: [PATCH 24/95] Bump version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index f49d971518..6bec70b51b 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.8.6", + "version": "10.8.7", "assemblyVersion": { "precision": "build" }, From 42912dd5c9072298811865eeb50eaf65a8f24ce7 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:44:18 +0200 Subject: [PATCH 25/95] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 71eeca0963..224d17f3d4 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 71eeca096330781e1a4f061aecaab528009234c5 +Subproject commit 224d17f3d43c8ca3037b807b40a8d7c86ce4827a From 4cfe4986285927f9ef2bf643673c38304d967995 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:45:04 +0200 Subject: [PATCH 26/95] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 71eeca0963..224d17f3d4 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 71eeca096330781e1a4f061aecaab528009234c5 +Subproject commit 224d17f3d43c8ca3037b807b40a8d7c86ce4827a From ea608cc630ff15737c689888f5f13bb44b6aced8 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:46:06 +0200 Subject: [PATCH 27/95] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 224d17f3d4..586bde9f23 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 224d17f3d43c8ca3037b807b40a8d7c86ce4827a +Subproject commit 586bde9f23168c08c519f143dbd7463bbe71eea5 From ed63b51e46b8e613851af237d9e81f9451a7381f Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:46:21 +0200 Subject: [PATCH 28/95] update backoffice submodule --- src/Umbraco.Web.UI.Client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client b/src/Umbraco.Web.UI.Client index 224d17f3d4..586bde9f23 160000 --- a/src/Umbraco.Web.UI.Client +++ b/src/Umbraco.Web.UI.Client @@ -1 +1 @@ -Subproject commit 224d17f3d43c8ca3037b807b40a8d7c86ce4827a +Subproject commit 586bde9f23168c08c519f143dbd7463bbe71eea5 From b814608c06e66d638ee203920da90f52946b0d78 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:34:23 +0200 Subject: [PATCH 29/95] Bump version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index cb406a4d22..ba35bbaff3 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "13.5.1", + "version": "13.5.2", "assemblyVersion": { "precision": "build" }, From 7787af2df1e4b906fac9c8b6b10446319774b155 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Thu, 10 Oct 2024 18:09:11 +0200 Subject: [PATCH 30/95] Fix install url detection (#17241) --- src/Umbraco.Core/Routing/UmbracoRequestPaths.cs | 5 ++++- .../Umbraco.Core/Routing/UmbracoRequestPathsTests.cs | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs index b70970673a..f31b9fca06 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -148,7 +148,10 @@ public class UmbracoRequestPaths /// /// Checks if the current uri is an install request /// - public bool IsInstallerRequest(string absPath) => absPath.InvariantStartsWith(_installPath); + public bool IsInstallerRequest(string absPath) => + absPath.InvariantEquals(_installPath) + || absPath.InvariantStartsWith(_installPath.EnsureEndsWith('/')) + || absPath.InvariantStartsWith(_installPath.EnsureEndsWith('?')); /// /// Rudimentary check to see if it's not a server side request diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs index d4856c2b12..9c0e4cbb16 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UmbracoRequestPathsTests.cs @@ -102,9 +102,15 @@ public class UmbracoRequestPathsTests [TestCase("http://www.domain.com/install/test/test", true)] [TestCase("http://www.domain.com/Install/test/test.aspx", true)] [TestCase("http://www.domain.com/install/test/test.js", true)] + [TestCase("http://www.domain.com/install?param=value", true)] [TestCase("http://www.domain.com/instal", false)] [TestCase("http://www.domain.com/umbraco", false)] [TestCase("http://www.domain.com/umbraco/umbraco", false)] + [TestCase("http://www.domain.com/installation", false)] + [TestCase("http://www.domain.com/installation/", false)] + [TestCase("http://www.domain.com/installation/test", false)] + [TestCase("http://www.domain.com/installation/test.js", false)] + [TestCase("http://www.domain.com/installation?param=value", false)] public void Is_Installer_Request(string input, bool expected) { var source = new Uri(input); From c3db3457e7cb8e41c66673aee19246051ece3e80 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 11 Oct 2024 09:45:01 +0200 Subject: [PATCH 31/95] Fix ContentStore locking exceptions in async code (#17246) * Add ContentCache test * Use SemaphoreSlim as write lock * Apply lock imrpovements to SnapDictionary * Obsolete unused MonitorLock --- src/Umbraco.Core/MonitorLock.cs | 1 + .../ContentStore.cs | 25 +++--- .../SnapDictionary.cs | 84 ++++++++++--------- .../PublishedCache/ContentCacheTests.cs | 77 +++++++++++++++++ 4 files changed, 136 insertions(+), 51 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs diff --git a/src/Umbraco.Core/MonitorLock.cs b/src/Umbraco.Core/MonitorLock.cs index 45dbdbbd10..d8885ed256 100644 --- a/src/Umbraco.Core/MonitorLock.cs +++ b/src/Umbraco.Core/MonitorLock.cs @@ -4,6 +4,7 @@ namespace Umbraco.Cms.Core; /// Provides an equivalent to the c# lock statement, to be used in a using block. /// /// Ie replace lock (o) {...} by using (new MonitorLock(o)) { ... } +[Obsolete("Use System.Threading.Lock instead. This will be removed in a future version.")] public class MonitorLock : IDisposable { private readonly bool _entered; diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 0230032dc2..d706301ff8 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -52,9 +52,9 @@ public class ContentStore // SnapDictionary has unit tests to ensure it all works correctly // For locking information, see SnapDictionary private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly object _rlocko = new(); private readonly IVariationContextAccessor _variationContextAccessor; - private readonly object _wlocko = new(); + private readonly object _rlocko = new(); + private readonly SemaphoreSlim _writeLock = new(1); private Task? _collectTask; private GenObj? _genObj; private long _liveGen; @@ -319,7 +319,7 @@ public class ContentStore private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -327,14 +327,16 @@ public class ContentStore private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); - - if (Monitor.IsEntered(_wlocko) is false) + if (_writeLock.Wait(_monitorTimeout)) + { + lockInfo.Taken = true; + } + else { throw new TimeoutException("Could not enter monitor before timeout in content store"); } @@ -344,6 +346,7 @@ public class ContentStore // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -374,6 +377,7 @@ public class ContentStore // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -409,7 +413,7 @@ public class ContentStore { if (lockInfo.Taken) { - Monitor.Exit(_wlocko); + _writeLock.Release(); } } } @@ -1817,7 +1821,7 @@ public class ContentStore // else we need to try to create a new gen ref // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -1829,8 +1833,7 @@ public class ContentStore } else if (_genObj.Gen != snapGen) { - throw new PanicException( - $"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); + throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); } } else diff --git a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs index b6c87e22bb..70bdcfe038 100644 --- a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs +++ b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs @@ -28,13 +28,9 @@ public class SnapDictionary // This class is optimized for many readers, few writers // Readers are lock-free - // NOTE - we used to lock _rlocko the long hand way with Monitor.Enter(_rlocko, ref lockTaken) but this has - // been replaced with a normal c# lock because that's exactly how the normal c# lock works, - // see https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/ - // for the readlock, there's no reason here to use the long hand way. private readonly ConcurrentDictionary> _items; private readonly object _rlocko = new(); - private readonly object _wlocko = new(); + private readonly SemaphoreSlim _writeLock = new(1); private Task? _collectTask; private GenObj? _genObj; private long _liveGen; @@ -187,7 +183,7 @@ public class SnapDictionary private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -195,14 +191,16 @@ public class SnapDictionary private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); - - if (Monitor.IsEntered(_wlocko) is false) + if (_writeLock.Wait(_monitorTimeout)) + { + lockInfo.Taken = true; + } + else { throw new TimeoutException("Could not enter the monitor before timeout in SnapDictionary"); } @@ -217,6 +215,7 @@ public class SnapDictionary // RuntimeHelpers.PrepareConstrainedRegions(); try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -244,43 +243,48 @@ public class SnapDictionary return; } - if (commit == false) + try { - lock (_rlocko) + if (commit == false) { - try + lock (_rlocko) { - } - finally - { - // forget about the temp. liveGen - _nextGen = false; - _liveGen -= 1; - } - } - - foreach (KeyValuePair> item in _items) - { - LinkedNode? link = item.Value; - if (link.Gen <= _liveGen) - { - continue; + try + { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution + } + finally + { + // forget about the temp. liveGen + _nextGen = false; + _liveGen -= 1; + } } - TKey key = item.Key; - if (link.Next == null) + foreach (KeyValuePair> item in _items) { - _items.TryRemove(key, out link); - } - else - { - _items.TryUpdate(key, link.Next, link); + LinkedNode? link = item.Value; + if (link.Gen <= _liveGen) + { + continue; + } + + TKey key = item.Key; + if (link.Next == null) + { + _items.TryRemove(key, out link); + } + else + { + _items.TryUpdate(key, link.Next, link); + } } } } - - // TODO: Shouldn't this be in a finally block? - Monitor.Exit(_wlocko); + finally + { + _writeLock.Release(); + } } #endregion @@ -434,7 +438,7 @@ public class SnapDictionary // else we need to try to create a new gen object // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -624,7 +628,7 @@ public class SnapDictionary public bool NextGen => _dict._nextGen; - public bool IsLocked => Monitor.IsEntered(_dict._wlocko); + public bool IsLocked => _dict._writeLock.CurrentCount == 0; public bool CollectAuto { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs new file mode 100644 index 0000000000..6507bd6cda --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PublishedCache; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class ContentCacheTests : UmbracoIntegrationTestWithContent +{ + private ContentStore GetContentStore() + { + var path = Path.Combine(GetRequiredService().LocalTempPath, "NuCache"); + Directory.CreateDirectory(path); + + var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); + var localContentDbExists = File.Exists(localContentDbPath); + var contentDataSerializer = new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()); + var localContentDb = BTree.GetTree(localContentDbPath, localContentDbExists, new NuCacheSettings(), contentDataSerializer); + + return new ContentStore( + GetRequiredService(), + GetRequiredService(), + LoggerFactory.CreateLogger(), + LoggerFactory, + GetRequiredService(), // new NoopPublishedModelFactory + localContentDb); + } + + private ContentNodeKit CreateContentNodeKit() + { + var contentData = new ContentDataBuilder() + .WithName("Content 1") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("welcomeText", "Welcome") + .WithPropertyData("welcomeText", "Welcome", "en-US") + .WithPropertyData("welcomeText", "Willkommen", "de") + .WithPropertyData("welcomeText", "Welkom", "nl") + .WithPropertyData("welcomeText2", "Welcome") + .WithPropertyData("welcomeText2", "Welcome", "en-US") + .WithPropertyData("noprop", "xxx") + .Build()) + .Build(); + + return ContentNodeKitBuilder.CreateWithContent( + ContentType.Id, + 1, + "-1,1", + draftData: contentData, + publishedData: contentData); + } + + [Test] + public async Task SetLocked() + { + var contentStore = GetContentStore(); + + using (contentStore.GetScopedWriteLock(ScopeProvider)) + { + var contentNodeKit = CreateContentNodeKit(); + + contentStore.SetLocked(contentNodeKit); + + // Try running the same operation again in an async task + await Task.Run(() => contentStore.SetLocked(contentNodeKit)); + } + } +} From 30b114d5389e1650aa462085a45aac0d7f42f4d5 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 11 Oct 2024 09:45:01 +0200 Subject: [PATCH 32/95] Fix ContentStore locking exceptions in async code (#17246) * Add ContentCache test * Use SemaphoreSlim as write lock * Apply lock imrpovements to SnapDictionary * Obsolete unused MonitorLock (cherry picked from commit c3db3457e7cb8e41c66673aee19246051ece3e80) --- src/Umbraco.Core/MonitorLock.cs | 1 + .../ContentStore.cs | 25 +++--- .../SnapDictionary.cs | 84 ++++++++++--------- .../PublishedCache/ContentCacheTests.cs | 77 +++++++++++++++++ 4 files changed, 136 insertions(+), 51 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs diff --git a/src/Umbraco.Core/MonitorLock.cs b/src/Umbraco.Core/MonitorLock.cs index 45dbdbbd10..d8885ed256 100644 --- a/src/Umbraco.Core/MonitorLock.cs +++ b/src/Umbraco.Core/MonitorLock.cs @@ -4,6 +4,7 @@ namespace Umbraco.Cms.Core; /// Provides an equivalent to the c# lock statement, to be used in a using block. /// /// Ie replace lock (o) {...} by using (new MonitorLock(o)) { ... } +[Obsolete("Use System.Threading.Lock instead. This will be removed in a future version.")] public class MonitorLock : IDisposable { private readonly bool _entered; diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 0230032dc2..d706301ff8 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -52,9 +52,9 @@ public class ContentStore // SnapDictionary has unit tests to ensure it all works correctly // For locking information, see SnapDictionary private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly object _rlocko = new(); private readonly IVariationContextAccessor _variationContextAccessor; - private readonly object _wlocko = new(); + private readonly object _rlocko = new(); + private readonly SemaphoreSlim _writeLock = new(1); private Task? _collectTask; private GenObj? _genObj; private long _liveGen; @@ -319,7 +319,7 @@ public class ContentStore private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -327,14 +327,16 @@ public class ContentStore private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); - - if (Monitor.IsEntered(_wlocko) is false) + if (_writeLock.Wait(_monitorTimeout)) + { + lockInfo.Taken = true; + } + else { throw new TimeoutException("Could not enter monitor before timeout in content store"); } @@ -344,6 +346,7 @@ public class ContentStore // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -374,6 +377,7 @@ public class ContentStore // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -409,7 +413,7 @@ public class ContentStore { if (lockInfo.Taken) { - Monitor.Exit(_wlocko); + _writeLock.Release(); } } } @@ -1817,7 +1821,7 @@ public class ContentStore // else we need to try to create a new gen ref // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -1829,8 +1833,7 @@ public class ContentStore } else if (_genObj.Gen != snapGen) { - throw new PanicException( - $"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); + throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); } } else diff --git a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs index b6c87e22bb..70bdcfe038 100644 --- a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs +++ b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs @@ -28,13 +28,9 @@ public class SnapDictionary // This class is optimized for many readers, few writers // Readers are lock-free - // NOTE - we used to lock _rlocko the long hand way with Monitor.Enter(_rlocko, ref lockTaken) but this has - // been replaced with a normal c# lock because that's exactly how the normal c# lock works, - // see https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/ - // for the readlock, there's no reason here to use the long hand way. private readonly ConcurrentDictionary> _items; private readonly object _rlocko = new(); - private readonly object _wlocko = new(); + private readonly SemaphoreSlim _writeLock = new(1); private Task? _collectTask; private GenObj? _genObj; private long _liveGen; @@ -187,7 +183,7 @@ public class SnapDictionary private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -195,14 +191,16 @@ public class SnapDictionary private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); - - if (Monitor.IsEntered(_wlocko) is false) + if (_writeLock.Wait(_monitorTimeout)) + { + lockInfo.Taken = true; + } + else { throw new TimeoutException("Could not enter the monitor before timeout in SnapDictionary"); } @@ -217,6 +215,7 @@ public class SnapDictionary // RuntimeHelpers.PrepareConstrainedRegions(); try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -244,43 +243,48 @@ public class SnapDictionary return; } - if (commit == false) + try { - lock (_rlocko) + if (commit == false) { - try + lock (_rlocko) { - } - finally - { - // forget about the temp. liveGen - _nextGen = false; - _liveGen -= 1; - } - } - - foreach (KeyValuePair> item in _items) - { - LinkedNode? link = item.Value; - if (link.Gen <= _liveGen) - { - continue; + try + { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution + } + finally + { + // forget about the temp. liveGen + _nextGen = false; + _liveGen -= 1; + } } - TKey key = item.Key; - if (link.Next == null) + foreach (KeyValuePair> item in _items) { - _items.TryRemove(key, out link); - } - else - { - _items.TryUpdate(key, link.Next, link); + LinkedNode? link = item.Value; + if (link.Gen <= _liveGen) + { + continue; + } + + TKey key = item.Key; + if (link.Next == null) + { + _items.TryRemove(key, out link); + } + else + { + _items.TryUpdate(key, link.Next, link); + } } } } - - // TODO: Shouldn't this be in a finally block? - Monitor.Exit(_wlocko); + finally + { + _writeLock.Release(); + } } #endregion @@ -434,7 +438,7 @@ public class SnapDictionary // else we need to try to create a new gen object // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -624,7 +628,7 @@ public class SnapDictionary public bool NextGen => _dict._nextGen; - public bool IsLocked => Monitor.IsEntered(_dict._wlocko); + public bool IsLocked => _dict._writeLock.CurrentCount == 0; public bool CollectAuto { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs new file mode 100644 index 0000000000..6507bd6cda --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PublishedCache; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class ContentCacheTests : UmbracoIntegrationTestWithContent +{ + private ContentStore GetContentStore() + { + var path = Path.Combine(GetRequiredService().LocalTempPath, "NuCache"); + Directory.CreateDirectory(path); + + var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); + var localContentDbExists = File.Exists(localContentDbPath); + var contentDataSerializer = new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()); + var localContentDb = BTree.GetTree(localContentDbPath, localContentDbExists, new NuCacheSettings(), contentDataSerializer); + + return new ContentStore( + GetRequiredService(), + GetRequiredService(), + LoggerFactory.CreateLogger(), + LoggerFactory, + GetRequiredService(), // new NoopPublishedModelFactory + localContentDb); + } + + private ContentNodeKit CreateContentNodeKit() + { + var contentData = new ContentDataBuilder() + .WithName("Content 1") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("welcomeText", "Welcome") + .WithPropertyData("welcomeText", "Welcome", "en-US") + .WithPropertyData("welcomeText", "Willkommen", "de") + .WithPropertyData("welcomeText", "Welkom", "nl") + .WithPropertyData("welcomeText2", "Welcome") + .WithPropertyData("welcomeText2", "Welcome", "en-US") + .WithPropertyData("noprop", "xxx") + .Build()) + .Build(); + + return ContentNodeKitBuilder.CreateWithContent( + ContentType.Id, + 1, + "-1,1", + draftData: contentData, + publishedData: contentData); + } + + [Test] + public async Task SetLocked() + { + var contentStore = GetContentStore(); + + using (contentStore.GetScopedWriteLock(ScopeProvider)) + { + var contentNodeKit = CreateContentNodeKit(); + + contentStore.SetLocked(contentNodeKit); + + // Try running the same operation again in an async task + await Task.Run(() => contentStore.SetLocked(contentNodeKit)); + } + } +} From 9f5867bdf815669e8c7e65942b023cfa53d8233f Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Fri, 11 Oct 2024 17:15:49 +0700 Subject: [PATCH 33/95] V14 QA Fixing the failing acceptance tests in the pipeline v14 (#17214) * Added more waits * Updated assert steps * Fixed api method name * Added more waits to avoid the failing test in window * Updated tests due to business changes * Added more waits to avoid the failing tests in window * Updated test due to Ui changes * Bumped version * Bumped version of test helper * Bumped version --- .../package-lock.json | 19 +++++++++---------- .../Umbraco.Tests.AcceptanceTest/package.json | 4 ++-- .../ContentWithAllowAtRoot.spec.ts | 2 -- .../Content/CultureAndHostnames.spec.ts | 1 + .../DataType/DataTypeFolder.spec.ts | 2 +- .../Packages/PackagesPackages.spec.ts | 1 + .../DocumentTypeTemplatesTab.spec.ts | 14 ++++++-------- .../Settings/PartialView/PartialView.spec.ts | 1 + .../Settings/Template/Templates.spec.ts | 1 + .../tests/DefaultConfig/Users/User.spec.ts | 2 +- 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 99eeb24f0a..4ce3aee211 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.20", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.86", + "@umbraco/json-models-builders": "^2.0.21", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.90", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -55,20 +55,19 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.20.tgz", - "integrity": "sha512-LmTtklne1HlhMr1nALA+P5FrjIC9jL3A6Pcxj4dy+IPnTgnU2vMYaQIfE8wwz5Z5fZ5AAhWx/Zpdi8xCTbVSuQ==", - "license": "MIT", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.21.tgz", + "integrity": "sha512-/8jf444B8XjYMJ4mdun6Nc1GD6z4VTOAMi/foRKNwDu6H+UEVx8KcFfwel+M1rQOz1OULyIsf+XJDYc7TMujOA==", "dependencies": { "camelize": "^1.0.1" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.86", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.86.tgz", - "integrity": "sha512-tF7nJCMgBJwaPtxWAuDOJ9lc3T11aO6ped9AxzAJTmzFdSJG16w8jzjWiNgCaU2xRsw5fRyos+I1YrFW249vLw==", + "version": "2.0.0-beta.90", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.90.tgz", + "integrity": "sha512-H55F9gttpQR8Fah77gxWxX3S/PY539r82tQRDbo6GgPHeilSVmLXcgesZ+2cgLaAQ406D6JGDvJVqIZukmEzuQ==", "dependencies": { - "@umbraco/json-models-builders": "2.0.20", + "@umbraco/json-models-builders": "2.0.21", "node-fetch": "^2.6.7" } }, diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index a6949b54fa..f41b929af2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -18,8 +18,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.20", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.86", + "@umbraco/json-models-builders": "^2.0.21", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.90", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" 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 index f028220533..595a4aa3e2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowAtRoot.spec.ts @@ -13,7 +13,6 @@ test.afterEach(async ({umbracoApi}) => { test('cannot create content if allow at root is disabled', async ({umbracoApi, umbracoUi}) => { // Arrange - const noAllowedDocumentTypeAvailableMessage = 'There are no allowed Document Types available for creating content here'; await umbracoApi.documentType.createDefaultDocumentType(documentTypeName); await umbracoUi.content.goToSection(ConstantHelper.sections.content); @@ -23,5 +22,4 @@ test('cannot create content if allow at root is disabled', async ({umbracoApi, u // Assert await umbracoUi.content.isDocumentTypeNameVisible(documentTypeName, false); - await umbracoUi.content.doesModalHaveText(noAllowedDocumentTypeAvailableMessage); }); \ No newline at end of file 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 369bbb7692..73ed97bd61 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/CultureAndHostnames.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/CultureAndHostnames.spec.ts @@ -108,6 +108,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/DataTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts index 2b5ce0857d..400027741c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts @@ -23,7 +23,7 @@ test('can create a data type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.createFolder(dataTypeFolderName); // Assert - expect(await umbracoApi.dataType.doesNameExist(dataTypeFolderName)).toBeTruthy(); + expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderName)).toBeTruthy(); }); test('can rename a data type folder', async ({umbracoApi, umbracoUi}) => { 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 59a68325cf..ab34386540 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts @@ -10,5 +10,6 @@ test('can see the marketplace', async ({umbracoUi}) => { await umbracoUi.package.clickPackagesTab(); // Assert + await umbracoUi.waitForTimeout(1000); await umbracoUi.package.isMarketPlaceIFrameVisible(); }); 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 1390bbdc4f..7308be62d8 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 @@ -16,7 +16,6 @@ test.afterEach(async ({umbracoApi}) => { test('can add an allowed template to a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.documentType.createDefaultDocumentType(documentTypeName); - await umbracoApi.template.ensureNameNotExists(templateName); const templateId = await umbracoApi.template.createDefaultTemplate(templateName); await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); @@ -39,28 +38,27 @@ test('can add an allowed template to a document type', {tag: '@smoke'}, async ({ test('can set an allowed template as default for document type', async ({umbracoApi, umbracoUi}) => { // Arrange - await umbracoApi.template.ensureNameNotExists(templateName); - const templateId = await umbracoApi.template.createDefaultTemplate(templateName); - await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId); + const secondTemplateName = 'Test Second Template'; + const firstTemplateId = await umbracoApi.template.createDefaultTemplate(templateName); + const secondTemplateId = await umbracoApi.template.createDefaultTemplate(secondTemplateName); + await umbracoApi.documentType.createDocumentTypeWithTwoAllowedTemplates(documentTypeName, firstTemplateId, secondTemplateId, true, firstTemplateId); await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); // Act await umbracoUi.documentType.goToDocumentType(documentTypeName); await umbracoUi.documentType.clickDocumentTypeTemplatesTab(); - await umbracoUi.documentType.clickDefaultTemplateButton(); + await umbracoUi.documentType.clickSetAsDefaultButton(); await umbracoUi.documentType.clickSaveButton(); // Assert await umbracoUi.documentType.isSuccessNotificationVisible(); const documentTypeData = await umbracoApi.documentType.getByName(documentTypeName); - expect(documentTypeData.allowedTemplates[0].id).toBe(templateId); - expect(documentTypeData.defaultTemplate.id).toBe(templateId); + expect(documentTypeData.defaultTemplate.id).toBe(secondTemplateId); }); // When removing a template, the defaultTemplateId is set to "" which is not correct test.skip('can remove an allowed template from a document type', async ({umbracoApi, umbracoUi}) => { // Arrange - await umbracoApi.template.ensureNameNotExists(templateName); const templateId = await umbracoApi.template.createDefaultTemplate(templateName); await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId); await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings); 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 9b1ad8f8be..6f095bc275 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 @@ -180,6 +180,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); 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 46fdf12ff5..adebbd26f7 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 @@ -201,6 +201,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); 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 87c69bb6a9..4f9c65b0e7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts @@ -117,7 +117,7 @@ test('can update culture for a user', async ({umbracoApi, umbracoUi}) => { // Act await umbracoUi.user.clickUserWithName(nameOfTheUser); - await umbracoUi.user.selectUserLanguage('Dansk'); + await umbracoUi.user.selectUserLanguage('Dansk (Danmark)'); await umbracoUi.user.clickSaveButton(); // Assert From da2a4d17138f0e8ee5e40cb8665aaf94ea620094 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Mon, 14 Oct 2024 11:57:52 +0700 Subject: [PATCH 34/95] V14 QA Added the acceptance tests for rendering content with textstring (#17247) * Added tests for textstring in the rendered content * Updated tests for rendering content with textstring * Added tests for rendering content with numeric * Added tests for rendering content with textarea * Removed tests * Bumped version * Make all tests for rendering content run in the pipeline * Make all smoke tests run in the pipeline --- .../RenderingContentWithTextstring.spec.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts 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 0000000000..9a394ffd07 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts @@ -0,0 +1,43 @@ +import {test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const dataTypeName = 'Textstring'; +const templateName = 'TestTemplateForContent'; +let dataTypeData; + +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}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const textstringValue = textstring.value; + await umbracoApi.document.createPublishedDocumentWithValue(contentName, textstringValue, dataTypeData.id, documentTypeName, templateName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + await umbracoUi.contentRender.doesContentRenderValueHaveText(textstringValue); + }); +} + From 05519761266f24e82f5fc3df05dcc578d7b1414d Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:26:55 +0700 Subject: [PATCH 35/95] V14 QA Added acceptance tests for notification message (#17195) * Updated step to verify the notification message * Added tests for notification message * Bumped version * Updated expected notification message * Bumped version --- .../DefaultConfig/Content/Content.spec.ts | 16 ++++---- .../DefaultConfig/DataType/DataType.spec.ts | 10 +++-- .../DataType/DataTypeFolder.spec.ts | 17 +++++--- .../Dictionary/Dictionary.spec.ts | 8 ++-- .../tests/DefaultConfig/Media/Media.spec.ts | 17 +++++--- .../Members/MemberGroups.spec.ts | 12 +++--- .../DefaultConfig/Members/Members.spec.ts | 22 +++++------ .../DocumentBlueprint.spec.ts | 9 +++-- .../DocumentType/DocumentType.spec.ts | 16 ++++---- .../DocumentType/DocumentTypeFolder.spec.ts | 12 +++--- .../Settings/Language/Language.spec.ts | 16 ++++---- .../Settings/MediaType/MediaType.spec.ts | 12 +++--- .../MediaType/MediaTypeFolder.spec.ts | 10 +++-- .../Settings/PartialView/PartialView.spec.ts | 20 +++++----- .../PartialView/PartialViewFolder.spec.ts | 14 +++---- .../Settings/Script/Script.spec.ts | 12 +++--- .../Settings/Script/ScriptFolder.spec.ts | 16 ++++---- .../Settings/Stylesheet/Stylesheet.spec.ts | 18 ++++----- .../Stylesheet/StylesheetFolder.spec.ts | 16 ++++---- .../Settings/Template/Templates.spec.ts | 30 +++++++------- .../tests/DefaultConfig/Users/User.spec.ts | 39 +++++++++---------- 21 files changed, 180 insertions(+), 162 deletions(-) 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 0c7a869eb0..693762a0ec 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'); }); @@ -156,7 +158,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/DataType/DataType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts index cf3b771973..12fddb5d0c 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 400027741c..e2177f6337 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,7 +23,8 @@ test('can create a data type folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.dataType.createFolder(dataTypeFolderName); // Assert - expect(await umbracoApi.dataType.doesFolderExist(dataTypeFolderName)).toBeTruthy(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderCreated); + expect(await umbracoApi.dataType.doesNameExist(dataTypeFolderName)).toBeTruthy(); }); test('can rename a data type folder', async ({umbracoApi, umbracoUi}) => { @@ -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.folderUpdated); 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.folderDeleted); expect(await umbracoApi.dataType.doesNameExist(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.folderCreated); 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.folderCreated); 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/Dictionary/Dictionary.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Dictionary/Dictionary.spec.ts index 68ad9ee39b..00b31a8838 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); @@ -64,7 +64,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/Media.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts index 803eb63c79..ea45f76b42 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'; @@ -45,7 +45,7 @@ test('can rename a media file', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); await umbracoUi.media.isTreeItemVisible(mediaFileName); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeTruthy(); }); @@ -73,7 +73,7 @@ for (const mediaFileType of mediaFileTypes) { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.media.isTreeItemVisible(mediaFileType.fileName); expect(await umbracoApi.media.doesNameExist(mediaFileType.fileName)).toBeTruthy(); @@ -93,7 +93,7 @@ test.skip('can delete a media file', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.deleteMediaItem(mediaFileName); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); await umbracoUi.media.isTreeItemVisible(mediaFileName, false); expect(await umbracoApi.media.doesNameExist(mediaFileName)).toBeFalsy(); }); @@ -110,7 +110,7 @@ test('can create a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderCreated); await umbracoUi.media.isTreeItemVisible(folderName); expect(await umbracoApi.media.doesNameExist(folderName)).toBeTruthy(); @@ -132,6 +132,7 @@ test.skip('can delete a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickConfirmToDeleteButton(); // Assert + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderDeleted); await umbracoUi.media.isTreeItemVisible(folderName, false); expect(await umbracoApi.media.doesNameExist(folderName)).toBeFalsy(); }); @@ -151,7 +152,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.isSuccessNotificationVisible(); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderCreated); await umbracoUi.media.isTreeItemVisible(parentFolderName); await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); await umbracoUi.media.isTreeItemVisible(folderName); @@ -192,6 +193,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(); @@ -212,6 +214,7 @@ 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); @@ -234,6 +237,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(); @@ -252,6 +256,7 @@ test('can empty the recycle bin', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickConfirmEmptyRecycleBinButton(); // Assert + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.emptiedRecycleBin); await umbracoUi.media.isItemVisibleInRecycleBin(mediaFileName, false); 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 80ce20aff8..43c10cd3fa 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 {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const memberGroupName = 'Test Member Group'; @@ -13,7 +13,7 @@ test.afterEach(async ({umbracoApi}) => { await umbracoApi.memberGroup.ensureNameNotExists(memberGroupName); }); -test('can create a member group', {tag: '@smoke'}, async ({page, umbracoApi, umbracoUi}) => { +test('can create a member group', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { // Act await umbracoUi.memberGroup.clickMemberGroupsTab(); await umbracoUi.memberGroup.clickMemberGroupCreateButton(); @@ -21,7 +21,7 @@ test('can create a member group', {tag: '@smoke'}, async ({page, umbracoApi, umb 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(); @@ -34,7 +34,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(); }); @@ -46,12 +46,12 @@ test.skip('cannot create member group with duplicate name', async ({umbracoApi, // Act await umbracoUi.memberGroup.clickMemberGroupsTab(); - await umbracoUi.memberGroup.clickCreateButton(true); + await umbracoUi.memberGroup.clickCreateButton(); await umbracoUi.memberGroup.enterMemberGroupName(memberGroupName); await umbracoUi.memberGroup.clickSaveButton(); // Assert - await umbracoUi.memberGroup.isErrorNotificationVisible(); + await umbracoUi.memberGroup.doesErrorNotificationHaveText(NotificationConstantHelper.error.duplicateName); }); // TODO: Remove skip when the front-end is ready. Currently it is impossible to delete a member group. 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 f44a164a20..ebecb2dd2a 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(); }); @@ -55,7 +55,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); }); @@ -73,7 +73,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); }); @@ -91,7 +91,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); }); @@ -111,7 +111,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}) => { @@ -129,7 +129,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); @@ -153,7 +153,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); @@ -197,7 +197,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); }); @@ -215,7 +215,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(); }); @@ -236,7 +236,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/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts index db71845ef1..67e5844fee 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 e12749b1cd..20f3f096ec 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); @@ -145,6 +145,6 @@ test('can delete a document type', {tag: '@smoke'}, async ({umbracoApi, umbracoU await umbracoUi.documentType.clickConfirmToDeleteButton(); // Assert - await umbracoUi.documentType.isSuccessNotificationVisible(); + await umbracoUi.documentType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.documentType.doesNameExist(documentTypeName)).toBeFalsy(); }); 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 a96999f441..320c7f60bf 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.folderCreated); 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.folderDeleted); 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.folderUpdated); 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.folderCreated); 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.folderCreated); 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/Language/Language.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts index 9d756daab3..fb1e9c7a7a 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 96d562206e..0690dac4ef 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 83006c9e54..7863d90fee 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.folderCreated); 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.folderDeleted); 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.folderUpdated); 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.folderCreated); 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.folderCreated); 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 6f095bc275..bd81a05d72 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 @@ -107,7 +107,7 @@ test.skip('can update a partial view content', {tag: '@smoke'}, async ({umbracoA 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); }); @@ -147,7 +147,7 @@ test.skip('can use query builder with Order By statement for a partial view', as 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); }); @@ -188,7 +188,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); }); @@ -210,7 +210,7 @@ test.skip('can insert dictionary item into a partial view', async ({umbracoApi, 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); }); @@ -230,7 +230,7 @@ test.skip('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); }); @@ -246,7 +246,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 58741a3d7a..cae5b85dc2 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.folderCreated); 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.folderCreated); 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.folderCreated); 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 282a503021..3c3f009838 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.saved); expect(await umbracoApi.script.doesNameExist(scriptName)).toBeTruthy(); expect(await umbracoApi.script.doesNameExist(wrongScriptName)).toBeFalsy(); }); 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 d8f105867d..e7453a2a4c 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'; @@ -24,7 +24,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.folderCreated); expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeTruthy(); await umbracoUi.script.isScriptRootTreeItemVisible(scriptFolderName); }); @@ -40,7 +40,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.folderDeleted); expect(await umbracoApi.script.doesFolderExist(scriptFolderName)).toBeFalsy(); await umbracoUi.script.isScriptRootTreeItemVisible(scriptFolderName, false, false); }); @@ -61,7 +61,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); @@ -83,7 +83,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.folderCreated); expect(await umbracoApi.script.doesNameExist(childFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName); @@ -106,7 +106,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.folderCreated); expect(await umbracoApi.script.doesNameExist(childOfChildFolderName)).toBeTruthy(); const scriptChildren = await umbracoApi.script.getChildren('/' + scriptFolderName + '/' + childFolderName); expect(scriptChildren[0].path).toBe('/' + scriptFolderName + '/' + childFolderName + '/' + childOfChildFolderName); @@ -131,7 +131,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); @@ -152,5 +152,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 43ee3af2d7..bd6de28725 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 edc2552fba..42bd719e56 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'; @@ -24,7 +24,7 @@ test('can create a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.waitForTimeout(1000); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderCreated); expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeTruthy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetFolderName); }); @@ -40,7 +40,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.folderDeleted); expect(await umbracoApi.stylesheet.doesFolderExist(stylesheetFolderName)).toBeFalsy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetFolderName, false, false); }); @@ -57,7 +57,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.folderCreated); expect(await umbracoApi.stylesheet.doesNameExist(childFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName); @@ -80,7 +80,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.folderCreated); expect(await umbracoApi.stylesheet.doesNameExist(childOfChildFolderName)).toBeTruthy(); const styleChildren = await umbracoApi.stylesheet.getChildren('/' + stylesheetFolderName + '/' + childFolderName); expect(styleChildren[0].path).toBe('/' + stylesheetFolderName + '/' + childFolderName + '/' + childOfChildFolderName); @@ -104,7 +104,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); @@ -133,7 +133,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); @@ -156,5 +156,5 @@ test('cannot delete non-empty folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.deleteFolder(); //Assert - await umbracoUi.stylesheet.isErrorNotificationVisible(); + await umbracoUi.stylesheet.doesErrorNotificationHaveText(NotificationConstantHelper.error.notEmpty); }); \ No newline at end of file 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 adebbd26f7..64eb299a94 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); }); @@ -42,7 +42,7 @@ test('can update content of a template', {tag: '@smoke'}, async ({umbracoApi, um 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 +62,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 +78,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 +98,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 +125,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 +169,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); }); @@ -209,7 +209,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); }); @@ -229,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); }); @@ -250,7 +250,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); }); @@ -292,7 +292,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); @@ -317,7 +317,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); }); @@ -337,7 +337,7 @@ test.skip('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); }); @@ -381,5 +381,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/User.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts index 4f9c65b0e7..ec223fa5c8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.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 nameOfTheUser = 'TestUser'; @@ -28,7 +28,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(); }); @@ -46,7 +46,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(); }); @@ -62,7 +62,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.clickUsersTabButton(); @@ -85,8 +85,7 @@ test('can add multiple user groups to a user', async ({umbracoApi, umbracoUi}) = await umbracoUi.user.clickSaveButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); - const userData = await umbracoApi.user.getByName(nameOfTheUser); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.user.doesUserContainUserGroupIds(nameOfTheUser, [userGroupWriters.id, userGroupTranslators.id])).toBeTruthy(); }); @@ -103,7 +102,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([]); }); @@ -121,7 +120,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); }); @@ -146,7 +145,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 @@ -181,7 +180,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 @@ -214,7 +213,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 @@ -239,7 +238,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 @@ -271,7 +270,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 @@ -300,7 +299,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 @@ -319,7 +318,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() }); @@ -336,7 +335,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(); }); @@ -422,7 +421,7 @@ test('can disable a user', async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickConfirmDisableButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(umbracoUi.user.isUserDisabledTextVisible()).toBeTruthy(); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.state).toBe(disabledStatus); @@ -442,7 +441,7 @@ test('can enable a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickConfirmEnableButton(); // Assert - await umbracoUi.user.isSuccessNotificationVisible(); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.userDisabled); 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); @@ -461,7 +460,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); }); @@ -478,7 +477,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.saved); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.avatarUrls).toHaveLength(0); }); From 5183391a869c5a253f051b759b1f8fc0bb41e7d8 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:07:25 +0200 Subject: [PATCH 36/95] Updated nuget package (#17286) --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 47df4cf510..1096af77d6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,7 @@ - + @@ -90,4 +90,4 @@ - \ No newline at end of file + From f4f83bccbe1319200f2c045228540af9542a50bc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 16 Oct 2024 10:25:17 +0200 Subject: [PATCH 37/95] Added an explicit dependency to Microsoft.Extensions.Caching.Memory to force it to use a non-vulnerable version (#17287) --- Directory.Packages.props | 5 ++++- src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj | 3 +++ .../Umbraco.Cms.Persistence.EFCore.csproj | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 43435394c1..fb5de6c698 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -91,5 +91,8 @@ + + + - \ No newline at end of file + diff --git a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj index f479e70901..51caf6043f 100644 --- a/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj +++ b/src/Umbraco.Cms.Api.Common/Umbraco.Cms.Api.Common.csproj @@ -15,6 +15,9 @@ + + + diff --git a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj index 4500c2812b..ed8a10f42f 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj +++ b/src/Umbraco.Cms.Persistence.EFCore/Umbraco.Cms.Persistence.EFCore.csproj @@ -7,6 +7,10 @@ + + + + From 2d71b5a63b45982553f7f7eb282821a6861ff41b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 16 Oct 2024 12:16:38 +0200 Subject: [PATCH 38/95] Updated image sharp to a non vulnerable version (#17290) --- src/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 4d6bf155d3..0ea425c69a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -48,7 +48,7 @@ - + @@ -64,4 +64,4 @@ - \ No newline at end of file + From a497f3f4ddc21d677753a577e5e09f441577617d Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:07:25 +0200 Subject: [PATCH 39/95] Updated nuget package (#17286) (cherry picked from commit 5183391a869c5a253f051b759b1f8fc0bb41e7d8) --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 47df4cf510..1096af77d6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,7 +20,7 @@ - + @@ -90,4 +90,4 @@ - \ No newline at end of file + From 378d4ecfef3a6f34ff849b7208419f0406523837 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 11 Oct 2024 09:45:01 +0200 Subject: [PATCH 40/95] Fix ContentStore locking exceptions in async code (#17246) * Add ContentCache test * Use SemaphoreSlim as write lock * Apply lock imrpovements to SnapDictionary * Obsolete unused MonitorLock (cherry picked from commit c3db3457e7cb8e41c66673aee19246051ece3e80) --- src/Umbraco.Core/MonitorLock.cs | 1 + .../ContentStore.cs | 25 +++--- .../SnapDictionary.cs | 84 ++++++++++--------- .../PublishedCache/ContentCacheTests.cs | 77 +++++++++++++++++ 4 files changed, 136 insertions(+), 51 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs diff --git a/src/Umbraco.Core/MonitorLock.cs b/src/Umbraco.Core/MonitorLock.cs index 45dbdbbd10..d8885ed256 100644 --- a/src/Umbraco.Core/MonitorLock.cs +++ b/src/Umbraco.Core/MonitorLock.cs @@ -4,6 +4,7 @@ namespace Umbraco.Cms.Core; /// Provides an equivalent to the c# lock statement, to be used in a using block. /// /// Ie replace lock (o) {...} by using (new MonitorLock(o)) { ... } +[Obsolete("Use System.Threading.Lock instead. This will be removed in a future version.")] public class MonitorLock : IDisposable { private readonly bool _entered; diff --git a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs index 0230032dc2..d706301ff8 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentStore.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentStore.cs @@ -52,9 +52,9 @@ public class ContentStore // SnapDictionary has unit tests to ensure it all works correctly // For locking information, see SnapDictionary private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; - private readonly object _rlocko = new(); private readonly IVariationContextAccessor _variationContextAccessor; - private readonly object _wlocko = new(); + private readonly object _rlocko = new(); + private readonly SemaphoreSlim _writeLock = new(1); private Task? _collectTask; private GenObj? _genObj; private long _liveGen; @@ -319,7 +319,7 @@ public class ContentStore private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -327,14 +327,16 @@ public class ContentStore private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); - - if (Monitor.IsEntered(_wlocko) is false) + if (_writeLock.Wait(_monitorTimeout)) + { + lockInfo.Taken = true; + } + else { throw new TimeoutException("Could not enter monitor before timeout in content store"); } @@ -344,6 +346,7 @@ public class ContentStore // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -374,6 +377,7 @@ public class ContentStore // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -409,7 +413,7 @@ public class ContentStore { if (lockInfo.Taken) { - Monitor.Exit(_wlocko); + _writeLock.Release(); } } } @@ -1817,7 +1821,7 @@ public class ContentStore // else we need to try to create a new gen ref // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -1829,8 +1833,7 @@ public class ContentStore } else if (_genObj.Gen != snapGen) { - throw new PanicException( - $"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); + throw new PanicException($"The generation {_genObj.Gen} does not equal the snapshot generation {snapGen}"); } } else diff --git a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs index b6c87e22bb..70bdcfe038 100644 --- a/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs +++ b/src/Umbraco.PublishedCache.NuCache/SnapDictionary.cs @@ -28,13 +28,9 @@ public class SnapDictionary // This class is optimized for many readers, few writers // Readers are lock-free - // NOTE - we used to lock _rlocko the long hand way with Monitor.Enter(_rlocko, ref lockTaken) but this has - // been replaced with a normal c# lock because that's exactly how the normal c# lock works, - // see https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/ - // for the readlock, there's no reason here to use the long hand way. private readonly ConcurrentDictionary> _items; private readonly object _rlocko = new(); - private readonly object _wlocko = new(); + private readonly SemaphoreSlim _writeLock = new(1); private Task? _collectTask; private GenObj? _genObj; private long _liveGen; @@ -187,7 +183,7 @@ public class SnapDictionary private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -195,14 +191,16 @@ public class SnapDictionary private void Lock(WriteLockInfo lockInfo, bool forceGen = false) { - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { throw new InvalidOperationException("Recursive locks not allowed"); } - Monitor.TryEnter(_wlocko, _monitorTimeout, ref lockInfo.Taken); - - if (Monitor.IsEntered(_wlocko) is false) + if (_writeLock.Wait(_monitorTimeout)) + { + lockInfo.Taken = true; + } + else { throw new TimeoutException("Could not enter the monitor before timeout in SnapDictionary"); } @@ -217,6 +215,7 @@ public class SnapDictionary // RuntimeHelpers.PrepareConstrainedRegions(); try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -244,43 +243,48 @@ public class SnapDictionary return; } - if (commit == false) + try { - lock (_rlocko) + if (commit == false) { - try + lock (_rlocko) { - } - finally - { - // forget about the temp. liveGen - _nextGen = false; - _liveGen -= 1; - } - } - - foreach (KeyValuePair> item in _items) - { - LinkedNode? link = item.Value; - if (link.Gen <= _liveGen) - { - continue; + try + { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution + } + finally + { + // forget about the temp. liveGen + _nextGen = false; + _liveGen -= 1; + } } - TKey key = item.Key; - if (link.Next == null) + foreach (KeyValuePair> item in _items) { - _items.TryRemove(key, out link); - } - else - { - _items.TryUpdate(key, link.Next, link); + LinkedNode? link = item.Value; + if (link.Gen <= _liveGen) + { + continue; + } + + TKey key = item.Key; + if (link.Next == null) + { + _items.TryRemove(key, out link); + } + else + { + _items.TryUpdate(key, link.Next, link); + } } } } - - // TODO: Shouldn't this be in a finally block? - Monitor.Exit(_wlocko); + finally + { + _writeLock.Release(); + } } #endregion @@ -434,7 +438,7 @@ public class SnapDictionary // else we need to try to create a new gen object // whether we are wlocked or not, noone can rlock while we do, // so _liveGen and _nextGen are safe - if (Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount == 0) { // write-locked, cannot use latest gen (at least 1) so use previous var snapGen = _nextGen ? _liveGen - 1 : _liveGen; @@ -624,7 +628,7 @@ public class SnapDictionary public bool NextGen => _dict._nextGen; - public bool IsLocked => Monitor.IsEntered(_dict._wlocko); + public bool IsLocked => _dict._writeLock.CurrentCount == 0; public bool CollectAuto { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs new file mode 100644 index 0000000000..6507bd6cda --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PublishedCache/ContentCacheTests.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Logging; +using NUnit.Framework; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache; +using Umbraco.Cms.Infrastructure.PublishedCache.DataSource; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PublishedCache; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class ContentCacheTests : UmbracoIntegrationTestWithContent +{ + private ContentStore GetContentStore() + { + var path = Path.Combine(GetRequiredService().LocalTempPath, "NuCache"); + Directory.CreateDirectory(path); + + var localContentDbPath = Path.Combine(path, "NuCache.Content.db"); + var localContentDbExists = File.Exists(localContentDbPath); + var contentDataSerializer = new ContentDataSerializer(new DictionaryOfPropertyDataSerializer()); + var localContentDb = BTree.GetTree(localContentDbPath, localContentDbExists, new NuCacheSettings(), contentDataSerializer); + + return new ContentStore( + GetRequiredService(), + GetRequiredService(), + LoggerFactory.CreateLogger(), + LoggerFactory, + GetRequiredService(), // new NoopPublishedModelFactory + localContentDb); + } + + private ContentNodeKit CreateContentNodeKit() + { + var contentData = new ContentDataBuilder() + .WithName("Content 1") + .WithProperties(new PropertyDataBuilder() + .WithPropertyData("welcomeText", "Welcome") + .WithPropertyData("welcomeText", "Welcome", "en-US") + .WithPropertyData("welcomeText", "Willkommen", "de") + .WithPropertyData("welcomeText", "Welkom", "nl") + .WithPropertyData("welcomeText2", "Welcome") + .WithPropertyData("welcomeText2", "Welcome", "en-US") + .WithPropertyData("noprop", "xxx") + .Build()) + .Build(); + + return ContentNodeKitBuilder.CreateWithContent( + ContentType.Id, + 1, + "-1,1", + draftData: contentData, + publishedData: contentData); + } + + [Test] + public async Task SetLocked() + { + var contentStore = GetContentStore(); + + using (contentStore.GetScopedWriteLock(ScopeProvider)) + { + var contentNodeKit = CreateContentNodeKit(); + + contentStore.SetLocked(contentNodeKit); + + // Try running the same operation again in an async task + await Task.Run(() => contentStore.SetLocked(contentNodeKit)); + } + } +} From 00563013b6624cb9caccec88f169e26cb6e5e12e Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:46:24 +0200 Subject: [PATCH 41/95] V14 QA Updated package E2E tests (#17236) * Updated tests * Cleaned package tests * Updated package test file * Bumped version * Added missing semicolons * Run all smoke tests * Run smoke tests --- .../fixtures/packageLibrary/package.xml | 2 - .../Packages/CreatedPackages.spec.ts | 236 ++++++++---------- .../Packages/InstalledPackages.spec.ts | 6 +- .../Packages/PackagesPackages.spec.ts | 1 - 4 files changed, 107 insertions(+), 138 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml b/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml index 72693f113d..f3a15e7154 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/tests/DefaultConfig/Packages/CreatedPackages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts index 0b72f77f0c..244aaa3f4b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts @@ -15,127 +15,122 @@ test.afterEach(async ({umbracoApi}) => { await umbracoApi.package.ensureNameNotExists(packageName); }); -test.skip('can create a empty package', {tag: '@smoke'}, async ({umbracoUi}) => { +test('can create a empty package', {tag: '@smoke'}, async ({umbracoUi}) => { // Act await umbracoUi.package.clickCreatePackageButton(); await umbracoUi.package.enterPackageName(packageName); - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickCreateButton(); // Assert + await umbracoUi.package.isSuccessNotificationVisible(); + await umbracoUi.package.clickCreatedTab(); await umbracoUi.package.isPackageNameVisible(packageName); }); -test.skip('can update package name', async ({umbracoApi, umbracoUi}) => { +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.clickSaveChangesToPackageButton(); + 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.skip('can delete a package', async ({umbracoApi, umbracoUi}) => { +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.clickDeleteExactLabel(); + await umbracoUi.package.clickConfirmToDeleteButton(); // Assert + await umbracoUi.package.clickCreatedTab(); 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}) => { +test('can create a package with content', async ({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.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); + await umbracoUi.package.clickAddContentToPackageButton(); await umbracoUi.package.clickLabelWithName(documentName); - await umbracoUi.package.clickChooseBtn(); - await umbracoUi.package.clickSaveChangesToPackageButton(); + 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(); - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(documentName + ' ' + documentId)).toBeTruthy(); + expect(umbracoUi.package.isButtonWithNameVisible(documentName)).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}) => { +test('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(); + await umbracoApi.media.ensureNameNotExists(mediaName); + const mediaId = await umbracoApi.media.createDefaultMediaFile(mediaName); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddMediaToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(mediaName); + await umbracoUi.media.selectMediaByName(mediaName); await umbracoUi.package.clickSubmitButton(); - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(mediaTypeName + ' ' + mediaId)).toBeTruthy(); + 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.mediaType.ensureNameNotExists(mediaTypeName); + await umbracoApi.media.ensureNameNotExists(mediaName); }); -test.skip('can create a package with document types', async ({umbracoApi, umbracoUi}) => { +test('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.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddDocumentTypeToPackageButton(); - await umbracoUi.package.clickCaretButton(); await umbracoUi.package.clickLabelWithName(documentTypeName); - await umbracoUi.package.clickSubmitButton(); - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(documentTypeName + ' ' + documentTypeId)).toBeTruthy(); + await umbracoUi.package.isSuccessNotificationVisible(); + expect(umbracoUi.package.isButtonWithNameVisible(documentTypeName)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); expect(packageData.documentTypes[0] == documentTypeId).toBeTruthy(); @@ -143,26 +138,23 @@ test.skip('can create a package with document types', async ({umbracoApi, umbrac 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}) => { +test('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.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddMediaTypeToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(mediaTypeName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickButtonWithName(mediaTypeName, true); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); - expect(umbracoUi.package.isButtonWithNameVisible(mediaTypeName + ' ' + mediaTypeId)).toBeTruthy(); + await umbracoUi.package.isSuccessNotificationVisible(); + expect(umbracoUi.package.isButtonWithNameVisible(mediaTypeName)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); expect(packageData.mediaTypes[0] == mediaTypeId).toBeTruthy(); @@ -170,25 +162,23 @@ test.skip('can create a package with media types', async ({umbracoApi, umbracoUi 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}) => { +test('can create a package with languages', async ({umbracoApi, umbracoUi}) => { // Arrange - const languageId = await umbracoApi.language.createDefaultDanishLanguage(); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); + 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.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddLanguageToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(languageName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickButtonWithName(languageName); + await umbracoUi.package.clickSubmitButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.isSuccessNotificationVisible(); expect(umbracoUi.package.isButtonWithNameVisible(languageName + ' ' + languageId)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); expect(packageData.languages[0] == languageId).toBeTruthy(); @@ -197,76 +187,69 @@ test.skip('can create a package with languages', async ({umbracoApi, umbracoUi}) 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}) => { +test('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(); + const dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(dictionaryName); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddDictionaryToPackageButton(); - await umbracoUi.package.clickCaretButton(); - await umbracoUi.package.clickLabelWithName(dictionaryName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickButtonWithName(dictionaryName); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.isSuccessNotificationVisible(); expect(umbracoUi.package.isButtonWithNameVisible(dictionaryName)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); - expect(packageData.dictionaryItems[0] == dictionaryName).toBeTruthy(); + expect(packageData.dictionaryItems[0] == dictionaryId).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}) => { +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); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddDataTypesToPackageButton(); - await umbracoUi.package.clickCaretButton(); await umbracoUi.package.clickLabelWithName(dataTypeName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + 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.dictionary.ensureNameNotExists(dataTypeName); + await umbracoApi.dataType.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}) => { +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); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddTemplatesToPackageButton(); - await umbracoUi.package.clickCaretButton(); await umbracoUi.package.clickLabelWithName(templateName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.isSuccessNotificationVisible(); expect(umbracoUi.package.isButtonWithNameVisible(templateName)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); expect(packageData.templates[0] == templateId).toBeTruthy(); @@ -275,24 +258,22 @@ test.skip('can create a package with templates', async ({umbracoApi, umbracoUi}) 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}) => { +test('can create a package with stylesheets', async ({umbracoApi, umbracoUi}) => { // Arrange - const stylesheetName = 'TestStylesheet'; + const stylesheetName = 'TestStylesheet.css'; + await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); const stylesheetId = await umbracoApi.stylesheet.createDefaultStylesheet(stylesheetName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddStylesheetToPackageButton(); - await umbracoUi.package.clickCaretButton(); await umbracoUi.package.clickLabelWithName(stylesheetName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.isSuccessNotificationVisible(); expect(umbracoUi.package.isButtonWithNameVisible(stylesheetName)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); expect(packageData.stylesheets[0] == stylesheetId).toBeTruthy(); @@ -301,24 +282,22 @@ test.skip('can create a package with stylesheets', async ({umbracoApi, umbracoUi 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}) => { +test('can create a package with scripts', async ({umbracoApi, umbracoUi}) => { // Arrange - const scriptName = 'TestScripts'; + const scriptName = 'TestScripts.js'; + await umbracoApi.script.ensureNameNotExists(scriptName); const scriptId = await umbracoApi.script.createDefaultScript(scriptName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddScriptToPackageButton(); - await umbracoUi.package.clickCaretButton(); await umbracoUi.package.clickLabelWithName(scriptName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.isSuccessNotificationVisible(); expect(umbracoUi.package.isButtonWithNameVisible(scriptName)).toBeTruthy(); const packageData = await umbracoApi.package.getByName(packageName); expect(packageData.scripts[0] == scriptId).toBeTruthy(); @@ -327,35 +306,30 @@ test.skip('can create a package with scripts', async ({umbracoApi, umbracoUi}) = 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}) => { +test('can create a package with partial views', async ({umbracoApi, umbracoUi}) => { // Arrange - const partialViewName = 'TestPartialView'; + const partialViewName = 'TestPartialView.cshtml'; const partialViewId = await umbracoApi.partialView.createDefaultPartialView(partialViewName); - await umbracoApi.package.createEmptyPackage(packageName); - await umbracoUi.reloadPage(); // Act - await umbracoUi.package.clickExistingPackageName(packageName); + await umbracoUi.package.clickCreatePackageButton(); + await umbracoUi.package.enterPackageName(packageName); await umbracoUi.package.clickAddPartialViewToPackageButton(); - await umbracoUi.package.clickCaretButton(); await umbracoUi.package.clickLabelWithName(partialViewName); - await umbracoUi.package.clickSubmitButton() - await umbracoUi.package.clickSaveChangesToPackageButton(); + await umbracoUi.package.clickChooseContainerButton(); + await umbracoUi.package.clickCreateButton(); // Assert - await umbracoUi.package.clickExistingPackageName(packageName); + 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.package.ensureNameNotExists(packageName); + await umbracoApi.partialView.ensureNameNotExists(partialViewName); }); -// 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}) => { +test('can download a package', async ({umbracoApi, umbracoUi}) => { // Arrange const packageId = await umbracoApi.package.createEmptyPackage(packageName); await umbracoUi.reloadPage(); 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 603b52d1e7..9164b496f5 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('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 ab34386540..6d3c159e95 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts @@ -1,6 +1,5 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -// 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}) => { // Arrange await umbracoUi.goToBackOffice(); From 2b3a91757d4e1fb50a869e2e746b481524e2be7e Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:26:36 +0200 Subject: [PATCH 42/95] V14 QA added user permission tests (#17226) * Added test * Small changes * Added content start node tests * Added media start node tests * Cleaned up * More updates * Cleaned up * Added wait * Cleaned up * Bumped helpers * Updated to run user tests * Fixed user tests * Bumped helpers * Added missing semicolon * Fixes based on comments * Run smoke tests --- .../Permissions/ContentStartNodes.spec.ts | 97 +++++++++++++++++++ .../Users/Permissions/MediaStartNodes.spec.ts | 88 +++++++++++++++++ .../Users/Permissions/UICulture.spec.ts | 49 ++++++++++ .../tests/DefaultConfig/Users/User.spec.ts | 19 ++-- 4 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts new file mode 100644 index 0000000000..24b50a707e --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts @@ -0,0 +1,97 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from '@playwright/test'; + +const testUser = { + name: 'Test User', + email: 'verySecureEmail@123.test', + password: 'verySecurePassword123', +}; + +const userGroupName = 'TestUserGroup'; + +const rootDocumentTypeName = 'RootDocumentType'; +const childDocumentTypeOneName = 'ChildDocumentTypeOne'; +const childDocumentTypeTwoName = 'ChildDocumentTypeTwo'; +let childDocumentTypeOneId = null; +let rootDocumentTypeId = null; + +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +let rootDocumentId = null; +let childDocumentOneId = null; +const rootDocumentName = 'RootDocument'; +const childDocumentOneName = 'ChildDocumentOne'; +const childDocumentTwoName = 'ChildDocumentTwo'; + +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); + 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.isContentVisible(rootDocumentName); + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.isChildContentVisible(rootDocumentName, childDocumentOneName); + await umbracoUi.content.isChildContentVisible(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.isContentVisible(rootDocumentName); + await umbracoUi.content.goToContentWithName(rootDocumentName); + await umbracoUi.content.isTextWithMessageVisible('The authenticated user do not have access to this resource'); + await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); + await umbracoUi.content.isChildContentVisible(rootDocumentName, childDocumentOneName); + await umbracoUi.content.isChildContentVisible(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.isContentVisible(rootDocumentName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts new file mode 100644 index 0000000000..f9dec39020 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts @@ -0,0 +1,88 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = { + name: 'Test User', + email: 'verySecureEmail@123.test', + password: 'verySecurePassword123', +}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +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.createUserGroupWithMediaSection(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.isMediaVisible(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.isMediaVisible(rootFolderName); + await umbracoUi.waitForTimeout(500); + await umbracoUi.media.goToMediaWithName(rootFolderName); + await umbracoUi.media.isTextWithMessageVisible('The authenticated user do not have access to this resource'); + 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.isMediaVisible(rootFolderName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts new file mode 100644 index 0000000000..5d94160d4a --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts @@ -0,0 +1,49 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = { + name: 'Test User', + email: 'verySecureEmail@123.test', + password: 'verySecurePassword123', +}; + +const userGroupName = 'TestUserGroup'; + +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; +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, false); +}); + +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/User.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts index ec223fa5c8..61977241e7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts @@ -4,13 +4,16 @@ 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); }); @@ -501,10 +504,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]); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToSection(ConstantHelper.sections.users); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(2); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.searchInUserSection(nameOfTheUser); // Assert @@ -519,10 +523,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]); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToSection(ConstantHelper.sections.users); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(2); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.filterByStatusName(inactiveStatus); // Assert @@ -537,10 +542,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]); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToSection(ConstantHelper.sections.users); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(2); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.filterByGroupName(defaultUserGroupName); // Assert @@ -554,16 +560,17 @@ 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]); + userCount = await umbracoApi.user.getUsersCount(); await umbracoUi.user.goToSection(ConstantHelper.sections.users); // Act - await umbracoUi.user.doesUserSectionContainUserAmount(2); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.orderByNewestUser(); // Assert // Wait for filtering to be done await umbracoUi.waitForTimeout(200); - await umbracoUi.user.doesUserSectionContainUserAmount(2); + await umbracoUi.user.doesUserSectionContainUserAmount(userCount); await umbracoUi.user.isUserWithNameTheFirstUserInList(nameOfTheUser); }); From 6d98162e19bfd34ed0de5eee31de51e8b5ca0123 Mon Sep 17 00:00:00 2001 From: Jason Elkin Date: Wed, 16 Oct 2024 14:08:37 +0100 Subject: [PATCH 43/95] Unlock form after unsuccessful save and publish. (#17285) (cherry picked from commit ee37ad0f4b1b952eb180c582e9954d35eafc013b) --- .../common/directives/components/content/edit.controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index e3b3ba413c..20f8c0b91a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -536,9 +536,9 @@ if (err && err.status === 400 && err.data) { // content was saved but is invalid. eventsService.emit("content.saved", { content: $scope.content, action: args.action, valid: false }); - eventsService.emit("form.unlock"); } + eventsService.emit("form.unlock"); return $q.reject(err); }); } @@ -759,7 +759,7 @@ //ensure error messages are displayed formHelper.showNotifications(err.data); clearNotifications($scope.content); - + handleHttpException(err); deferred.reject(err); }); From 1581eb61d37068365d910677d80d08b138c561fb Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:53:10 +0200 Subject: [PATCH 44/95] V15: Rich Text Editor links do not work with query strings and anchors (#17288) * fix: anchors and query strings do not work Since the change from UDIs to localLinks in href, the pattern matched a little too much in the href section completely ignoring any "extras" such as querystrings and anchors after the locallink, which meant that the locallink did not get replaced at all if they were present. This is fixed by limiting the regexp a bit. * fix: legacy links do not follow the same regexp as new links Because we are no longer matching the whole `href` attribute but only some of its contents, we need to fix up the old pattern. It has been extended with matching groups that follow the same pattern as the new links. * feat: allow a-tags to be multiline example: ```html Test ``` * fix: split regex into two parts: first a tokenizer for a-tags and then a type-finder * fix: ensure only "document" and "media" are matching to speed up the pattern * feat: allow a-tags to be multiline (cherry picked from commit 35e8f2e4604b265ccc4059bc007bce588cb73553) --- .../Templates/HtmlLocalLinkParser.cs | 76 +++++++++---------- .../Templates/HtmlLocalLinkParserTests.cs | 36 ++++++++- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index c79506fb5f..9dc302c218 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -14,11 +14,15 @@ public sealed class HtmlLocalLinkParser // media // other page internal static readonly Regex LocalLinkTagPattern = new( - @"document|media)['""].*?(?href=[""']/{localLink:(?[a-fA-F0-9-]+)})[""'])|((?href=[""']/{localLink:(?[a-fA-F0-9-]+)})[""'].*?type=(['""])(?document|media)(?:['""])))|(?:(?:type=['""](?document|media)['""])|(?:(?href=[""']/{localLink:[a-fA-F0-9-]+})[""'])))[^>]*>", + @"\/?{localLink:(?[a-fA-F0-9-]+)})[^>]*?>", + RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline); + + internal static readonly Regex TypePattern = new( + """type=['"](?(?:media|document))['"]""", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); internal static readonly Regex LocalLinkPattern = new( - @"href=""[/]?(?:\{|\%7B)localLink:([a-zA-Z0-9-://]+)(?:\}|\%7D)", + @"href=['""](?\/?(?:\{|\%7B)localLink:(?[a-zA-Z0-9-://]+)(?:\}|\%7D))", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); private readonly IPublishedUrlProvider _publishedUrlProvider; @@ -84,24 +88,20 @@ public sealed class HtmlLocalLinkParser { if (tagData.Udi is not null) { - var newLink = "#"; - if (tagData.Udi?.EntityType == Constants.UdiEntityType.Document) + var newLink = tagData.Udi?.EntityType switch { - newLink = _publishedUrlProvider.GetUrl(tagData.Udi.Guid); - } - else if (tagData.Udi?.EntityType == Constants.UdiEntityType.Media) - { - newLink = _publishedUrlProvider.GetMediaUrl(tagData.Udi.Guid); - } - + Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(tagData.Udi.Guid), + Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(tagData.Udi.Guid), + _ => "" + }; text = StripTypeAttributeFromTag(text, tagData.Udi!.EntityType); - text = text.Replace(tagData.TagHref, "href=\"" + newLink); + text = text.Replace(tagData.TagHref, newLink); } else if (tagData.IntId.HasValue) { var newLink = _publishedUrlProvider.GetUrl(tagData.IntId.Value); - text = text.Replace(tagData.TagHref, "href=\"" + newLink); + text = text.Replace(tagData.TagHref, newLink); } } @@ -109,7 +109,7 @@ public sealed class HtmlLocalLinkParser } // under normal circumstances, the type attribute is preceded by a space - // to cover the rare occasion where it isn't, we first replace with a a space and then without. + // to cover the rare occasion where it isn't, we first replace with a space and then without. private string StripTypeAttributeFromTag(string tag, string type) => tag.Replace($" type=\"{type}\"", string.Empty) .Replace($"type=\"{type}\"", string.Empty); @@ -119,21 +119,22 @@ public sealed class HtmlLocalLinkParser MatchCollection localLinkTagMatches = LocalLinkTagPattern.Matches(text); foreach (Match linkTag in localLinkTagMatches) { - if (linkTag.Groups.Count < 1) + if (Guid.TryParse(linkTag.Groups["guid"].Value, out Guid guid) is false) { continue; } - if (Guid.TryParse(linkTag.Groups["guid"].Value, out Guid guid) is false) + // Find the type attribute + Match typeMatch = TypePattern.Match(linkTag.Value); + if (typeMatch.Success is false) { continue; } yield return new LocalLinkTag( null, - new GuidUdi(linkTag.Groups["type"].Value, guid), - linkTag.Groups["locallink"].Value, - linkTag.Value); + new GuidUdi(typeMatch.Groups["type"].Value, guid), + linkTag.Groups["locallink"].Value); } // also return legacy results for values that have not been migrated @@ -150,25 +151,26 @@ public sealed class HtmlLocalLinkParser MatchCollection tags = LocalLinkPattern.Matches(text); foreach (Match tag in tags) { - if (tag.Groups.Count > 0) + if (tag.Groups.Count <= 0) { - var id = tag.Groups[1].Value; // .Remove(tag.Groups[1].Value.Length - 1, 1); + continue; + } - // The id could be an int or a UDI - if (UdiParser.TryParse(id, out Udi? udi)) - { - var guidUdi = udi as GuidUdi; - if (guidUdi is not null) - { - yield return new LocalLinkTag(null, guidUdi, tag.Value, null); - } - } + var id = tag.Groups["guid"].Value; - if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) + // The id could be an int or a UDI + if (UdiParser.TryParse(id, out Udi? udi)) + { + if (udi is GuidUdi guidUdi) { - yield return new LocalLinkTag (intId, null, tag.Value, null); + yield return new LocalLinkTag(null, guidUdi, tag.Groups["locallink"].Value); } } + + if (int.TryParse(id, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intId)) + { + yield return new LocalLinkTag (intId, null, tag.Groups["locallink"].Value); + } } } @@ -181,20 +183,10 @@ public sealed class HtmlLocalLinkParser TagHref = tagHref; } - public LocalLinkTag(int? intId, GuidUdi? udi, string tagHref, string? fullTag) - { - IntId = intId; - Udi = udi; - TagHref = tagHref; - FullTag = fullTag; - } - public int? IntId { get; } public GuidUdi? Udi { get; } public string TagHref { get; } - - public string? FullTag { get; } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs index 6ef0f74e2f..43e4eccedb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Templates/HtmlLocalLinkParserTests.cs @@ -119,6 +119,34 @@ public class HtmlLocalLinkParserTests [TestCase( "world", "world")] + [TestCase( + "

world

world

", + "

world

world

")] + + // attributes order should not matter + [TestCase( + "world", + "world")] + [TestCase( + "world", + "world")] + [TestCase( + "world", + "world")] + + // anchors and query strings + [TestCase( + "world", + "world")] + [TestCase( + "world", + "world")] + + // custom type ignored + [TestCase( + "world", + "world")] + // legacy [TestCase( "hello href=\"{localLink:1234}\" world ", @@ -129,9 +157,15 @@ public class HtmlLocalLinkParserTests [TestCase( "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")] + [TestCase( + "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}#anchor\" world ", + "hello href=\"/my-test-url#anchor\" world ")] [TestCase( "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/1001/my-image.jpg\" world ")] + [TestCase( + "hello href='{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}' world ", + "hello href='/media/1001/my-image.jpg' world ")] // This one has an invalid char so won't match. [TestCase( @@ -139,7 +173,7 @@ public class HtmlLocalLinkParserTests "hello href=\"{localLink:umb^://document/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")] [TestCase( "hello href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", - "hello href=\"#\" world ")] + "hello href=\"\" world ")] public void ParseLocalLinks(string input, string result) { // setup a mock URL provider which we'll use for testing From ba1080541b8039fde6f0bb8bc36237580f5f8ea1 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:59:41 +0200 Subject: [PATCH 45/95] Updated to string.empty (#17294) --- src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs index 9dc302c218..b8d6b3f3c5 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -92,7 +92,7 @@ public sealed class HtmlLocalLinkParser { Constants.UdiEntityType.Document => _publishedUrlProvider.GetUrl(tagData.Udi.Guid), Constants.UdiEntityType.Media => _publishedUrlProvider.GetMediaUrl(tagData.Udi.Guid), - _ => "" + _ => string.Empty, }; text = StripTypeAttributeFromTag(text, tagData.Udi!.EntityType); From a8f56311449ab8e0490b8e031e35760cd0645615 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 17 Oct 2024 11:25:02 +0200 Subject: [PATCH 46/95] V13: Update @umbraco-ui/uui to 1.11.0 (#17281) * build(deps): update @umbraco-ui/uui from 1.7.1 to 1.11.0 * fix: umb-login-element no attributes Cherry-picked a fix from V14 where the custom login input component was no longer needed, which was fixed because it errors out. This simplifies the login form. * cherry-pick code to handle 'enter' click from v14 --- src/Umbraco.Web.UI.Client/package-lock.json | 982 +++++++++--------- src/Umbraco.Web.UI.Client/package.json | 4 +- src/Umbraco.Web.UI.Login/package-lock.json | 952 ++++++++--------- src/Umbraco.Web.UI.Login/package.json | 6 +- src/Umbraco.Web.UI.Login/src/auth-styles.css | 38 +- src/Umbraco.Web.UI.Login/src/auth.element.ts | 41 +- .../src/components/login-input.element.ts | 64 -- .../components/pages/login.page.element.ts | 13 + src/Umbraco.Web.UI.Login/src/index.ts | 1 - 9 files changed, 1027 insertions(+), 1074 deletions(-) delete mode 100644 src/Umbraco.Web.UI.Login/src/components/login-input.element.ts diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 4d87f71a60..18c7fcd688 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -7,8 +7,8 @@ "name": "ui", "dependencies": { "@microsoft/signalr": "7.0.12", - "@umbraco-ui/uui": "1.7.1", - "@umbraco-ui/uui-css": "1.7.0", + "@umbraco-ui/uui": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0", "ace-builds": "1.31.1", "angular": "1.8.3", "angular-animate": "1.8.3", @@ -2080,9 +2080,9 @@ "dev": true }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", - "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz", + "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ==", "peer": true }, "node_modules/@lit/reactive-element": { @@ -2253,814 +2253,814 @@ "peer": true }, "node_modules/@umbraco-ui/uui": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.7.1.tgz", - "integrity": "sha512-wHGMW8NQaWJTdbbb7r03sah2Esab4Iy8bFWaTU+UtnrOpNsZclPwxZ3kZcjHnFu32xDFFBF0+iQiCki8Uy4dkA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.11.0.tgz", + "integrity": "sha512-1mX7adcpAZRswPA1p64kqE83Rg5cbZsYM/b/OyUcObaL2cIuBCVvjjuUjgkL2el993GptIzl05XVocdj1dDCeQ==", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.7.0", - "@umbraco-ui/uui-avatar": "1.7.0", - "@umbraco-ui/uui-avatar-group": "1.7.0", - "@umbraco-ui/uui-badge": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-boolean-input": "1.7.0", - "@umbraco-ui/uui-box": "1.7.0", - "@umbraco-ui/uui-breadcrumbs": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-button-group": "1.7.0", - "@umbraco-ui/uui-button-inline-create": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0", - "@umbraco-ui/uui-card-block-type": "1.7.0", - "@umbraco-ui/uui-card-content-node": "1.7.0", - "@umbraco-ui/uui-card-media": "1.7.0", - "@umbraco-ui/uui-card-user": "1.7.0", - "@umbraco-ui/uui-caret": "1.7.0", - "@umbraco-ui/uui-checkbox": "1.7.0", - "@umbraco-ui/uui-color-area": "1.7.0", - "@umbraco-ui/uui-color-picker": "1.7.0", - "@umbraco-ui/uui-color-slider": "1.7.0", - "@umbraco-ui/uui-color-swatch": "1.7.0", - "@umbraco-ui/uui-color-swatches": "1.7.0", - "@umbraco-ui/uui-combobox": "1.7.1", - "@umbraco-ui/uui-combobox-list": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0", - "@umbraco-ui/uui-dialog": "1.7.0", - "@umbraco-ui/uui-dialog-layout": "1.7.0", - "@umbraco-ui/uui-file-dropzone": "1.7.0", - "@umbraco-ui/uui-file-preview": "1.7.0", - "@umbraco-ui/uui-form": "1.7.0", - "@umbraco-ui/uui-form-layout-item": "1.7.0", - "@umbraco-ui/uui-form-validation-message": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-icon-registry": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0", - "@umbraco-ui/uui-input": "1.7.0", - "@umbraco-ui/uui-input-file": "1.7.1", - "@umbraco-ui/uui-input-lock": "1.7.1", - "@umbraco-ui/uui-input-password": "1.7.0", - "@umbraco-ui/uui-keyboard-shortcut": "1.7.0", - "@umbraco-ui/uui-label": "1.7.0", - "@umbraco-ui/uui-loader": "1.7.0", - "@umbraco-ui/uui-loader-bar": "1.7.0", - "@umbraco-ui/uui-loader-circle": "1.7.0", - "@umbraco-ui/uui-menu-item": "1.7.0", - "@umbraco-ui/uui-modal": "1.7.0", - "@umbraco-ui/uui-pagination": "1.7.1", - "@umbraco-ui/uui-popover": "1.7.0", - "@umbraco-ui/uui-popover-container": "1.7.0", - "@umbraco-ui/uui-progress-bar": "1.7.0", - "@umbraco-ui/uui-radio": "1.7.0", - "@umbraco-ui/uui-range-slider": "1.7.0", - "@umbraco-ui/uui-ref": "1.7.0", - "@umbraco-ui/uui-ref-list": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0", - "@umbraco-ui/uui-ref-node-data-type": "1.7.0", - "@umbraco-ui/uui-ref-node-document-type": "1.7.0", - "@umbraco-ui/uui-ref-node-form": "1.7.0", - "@umbraco-ui/uui-ref-node-member": "1.7.0", - "@umbraco-ui/uui-ref-node-package": "1.7.0", - "@umbraco-ui/uui-ref-node-user": "1.7.0", - "@umbraco-ui/uui-scroll-container": "1.7.0", - "@umbraco-ui/uui-select": "1.7.0", - "@umbraco-ui/uui-slider": "1.7.0", - "@umbraco-ui/uui-symbol-expand": "1.7.0", - "@umbraco-ui/uui-symbol-file": "1.7.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.7.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.7.0", - "@umbraco-ui/uui-symbol-folder": "1.7.0", - "@umbraco-ui/uui-symbol-lock": "1.7.0", - "@umbraco-ui/uui-symbol-more": "1.7.0", - "@umbraco-ui/uui-symbol-sort": "1.7.0", - "@umbraco-ui/uui-table": "1.7.0", - "@umbraco-ui/uui-tabs": "1.7.1", - "@umbraco-ui/uui-tag": "1.7.0", - "@umbraco-ui/uui-textarea": "1.7.0", - "@umbraco-ui/uui-toast-notification": "1.7.1", - "@umbraco-ui/uui-toast-notification-container": "1.7.1", - "@umbraco-ui/uui-toast-notification-layout": "1.7.0", - "@umbraco-ui/uui-toggle": "1.7.0", - "@umbraco-ui/uui-visually-hidden": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.7.0.tgz", - "integrity": "sha512-Lw067iEU4DihiOsL3cg2QqE4x7B7bqjYQK0EouBbD+mhJaE2IOw5eve2UIBN1KU/iQ+7V9q4qa++is1nitvUWA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button-group": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-button-group": "1.11.0" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.7.0.tgz", - "integrity": "sha512-cW3qTTarFqXK4Ze5xMERo9pj3pRRKTvTDB57a5uA0gQ1/70uhgPnozWSX7EK22ml4w/5pmtxXXgRKfSiU9DGtQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.7.0.tgz", - "integrity": "sha512-TFDR0Mb+ug1NzVXq9RnxMiQ9pcxBcmzfOoxpR1NWMB/sAgNs/H/pTTqjieLel0/A5Am9q//8f7f9vmhTPpybGg==", + "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==", "dependencies": { - "@umbraco-ui/uui-avatar": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-avatar": "1.11.0", + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.7.0.tgz", - "integrity": "sha512-cdPHjXMag8KkYLaWfyYfp9N1qqG+th2Ijx7ms8EpTHAX2gtU1L/A3ShxWox0Ck1TJ75jrW66+HrqiMwDOmbn6g==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.11.0.tgz", + "integrity": "sha512-7VMZzUZ+CYaFhsCe8XS8VgadBhXZtJh0ilZ695YG9Q9IAbAVyeART59VwRzO/1kS0hfCj10DPEKp8IPMbePWEA==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.7.0.tgz", - "integrity": "sha512-66aDdgTrq2nx4BNzM9A/lc9dZYz/fyX5OVpkQDRsrpYeOLJMN3oOnE7aChIdBNW3I9lfVNJf6fh0iL27K5JCiQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.11.0.tgz", + "integrity": "sha512-w7HQDNtEt0qnu+psrwxvrdNxUT08qZ1fYygqH9yeKFyE5GMDvYlL1TWU696Lo72LTbTdSMm/ka9b2QBJv1ZJxA==", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.7.0.tgz", - "integrity": "sha512-Hp6wOFqFLaZU0oW63GlMJ8s4va/TG+i7Sjs0qT91//5iJhJtpvgwY3j4ARoDfk0d8rKRIapiPT+hNMo9xr1sfQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.7.0.tgz", - "integrity": "sha512-1P13tsVJXPEpMiHrw1FmsM0dvCLce8DevZAcP1ArDwtqWrwdArR2eRwlhVEZYu2MJkR2tESE3XGTaSOWHyC8og==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.11.0.tgz", + "integrity": "sha512-gYiERouKMpFy/n/6LDh9ckzWpUa2vBmCsWS41Gskct3WZNSVdApZ3g2yvE9ZoJoJB2Q26JfbKShuw0BaJkEFxg==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.7.0.tgz", - "integrity": "sha512-y0sB4UypNwCle9qPlQ7Y++a4BkmFpn9vSTeJ6WRWueVyjKT99icmCV1c8/Q47blMajp0FLG2/ajevxg/aZSO4Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.11.0.tgz", + "integrity": "sha512-wRTtuZAKb0z2Mi3P3wb1CcIO1ExnnFD8vCsHxiTEAjb2e2VzEaEwnnugHnr8chxlOKiTPyX8NtsBXDLTnL/TRA==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.7.1.tgz", - "integrity": "sha512-z2nZccn/Hel2QvytWVejDzqjNPRLJ/jLgCmLpgHoKU2IlckEgZqy4wxKcgH2Iu2bJ+wgIwpAAmlidLK0LX0tCw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.7.0.tgz", - "integrity": "sha512-CM0sytzzEXiDmFfB6GXnmQw5LzCNuwSo66BC6zYI4cg1+mk2a1UBu1Z8CVpvS3tsTkzk/nGd/ZFKkoIziDVKJg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.7.0.tgz", - "integrity": "sha512-SVep/tcsTJuO8jvZIX0e3EOaY1S+sOk0ZFmq+HxUJDt6csFjXsqJO48DjIon1AKq95ATTM9Iqs/hPSDVHrqNvw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.7.0.tgz", - "integrity": "sha512-BBWJ62UH1dmcHvZ3H0fRCnM9c+ebQMNaZlGDDWP5lPfv+2KjXXkLRuj6tPUthHa53e5Rf6AAKjKsjRssM4dsLQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.7.0.tgz", - "integrity": "sha512-SrLgooo2imluSV8S3bp+0kA2K7zuMDAXZTuzQJRf2hzq208In65D5rBxn8OcEJsGD3lHMp6+w8rg8Ol5NlEbXA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-card": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.7.0.tgz", - "integrity": "sha512-wkb9BaUfuZkrMczsm1q4vuP0zSOp0gfiiiXCxFRDNmWJc3jKiL3zF619PzviEZrz10/f7WRnA7MLfDgsAmQpAQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-card": "1.11.0", + "@umbraco-ui/uui-icon": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.7.0.tgz", - "integrity": "sha512-Jwli2j//U1v4zG5fvkrekduf3qCa5w0RNP28RBxeqqQKzO8B5UpWqIP86/qaV7hvlp/ZuTCYrdkeWLgUV85tBg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.11.0.tgz", + "integrity": "sha512-uOdN0iu5OKsOtxhTSE8epuUMo2iXq6FEVqBPQBHAmAFELDFyNf2UBwnBxnrTuU6RJ0jbGuLTqQQM7Gv8vD6Kjg==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0", - "@umbraco-ui/uui-symbol-file": "1.7.0", - "@umbraco-ui/uui-symbol-folder": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.7.0.tgz", - "integrity": "sha512-4fBXEICxi4ICAM2wn40DrUV1pPGSDFJmzacOA1PXxb1pzQjxw/hb/hnu96xqjHscX+bUAWnWHkb60RnrWmmcsg==", + "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==", "dependencies": { - "@umbraco-ui/uui-avatar": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0" + "@umbraco-ui/uui-avatar": "1.11.0", + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-card": "1.11.0" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.7.0.tgz", - "integrity": "sha512-sVWUQGaLCAwhFH5mmE83+cwDjTyZamRWHgmVakTac2L9qYkwhTwzRgIol1t4i0DQMDFd4oLZ1zq+ysWvAOCmmQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.7.0.tgz", - "integrity": "sha512-7bY8FgSEscGtMYf0EtvmU4XuchV8bdjs+gwBKCVYogAELDdKbCTxWI6/HRqR6wDUOltpP1okFYN6rISugkUKtw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.11.0.tgz", + "integrity": "sha512-bOfJXJ5LMiGCMD37A3mzYjiGTIvzjREN2AhtqGLbwcrAgj662WVhw0aQobo2+iIwaMUIAvl3kNS8930XDeUe/A==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-boolean-input": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-boolean-input": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.7.0.tgz", - "integrity": "sha512-7FashEB3hoh9p833gEhseq1t2mICVzb5zRe+FJ+vKFnTI2uuIRLjwD0pqSwmVAxoFCPgb81B6V5yH/pSrrzZEQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", + "@umbraco-ui/uui-base": "1.11.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.7.0.tgz", - "integrity": "sha512-9JYlgg6i/gxwTIYsCJ8QnSjhZzZjJBiu2HZwDJ/2rm8uy/jNmbCf5aK+WHR7RbwMGNrF4/X/58t5woBzwSMUIA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-popover-container": "1.7.0", + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-popover-container": "1.11.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.7.0.tgz", - "integrity": "sha512-DVyYeZsBG35430Cay6Dv8oO7dvi+aow6fVAJDHA4+CXdOSet4RTLO3oc1i51JwDmBiBhtLKGfo/wflrFxyfr0w==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.7.0.tgz", - "integrity": "sha512-hp4Oicv7eLMvSn6jUerjDkYY6R/8JCRxbXabfbfZOZ/YwocSLN6DBc0nxlb/W8IETy26VCEFXH+tYKvZbsAB2Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.11.0.tgz", + "integrity": "sha512-BeCyW9FyVmjE2W8u3k5bPwkRUIVbudK2q9VTKmIcnkwsZz8wv6dDpFoFb92So8YSzMhdiVIRQ14fnphHwMHfWQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0", + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.7.0.tgz", - "integrity": "sha512-XIPP4BhaRB6nL3HAt2KRsEeslq/I2hMl8eQzgbz8y9V6yf7uq8q0OCMqQy2XB6bQ48N+sOqXfjKLPIT4yTIH7A==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-color-swatch": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-color-swatch": "1.11.0" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.7.1.tgz", - "integrity": "sha512-4nbsRyqJO+rifoug+1PlWA8oI1L6f3aj7P/p9UT4pgcT8mpJ5Fv70XaFXMPEaCWh8HgZLsvMKDClXNzHXlvcLA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-combobox-list": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-popover-container": "1.7.0", - "@umbraco-ui/uui-scroll-container": "1.7.0", - "@umbraco-ui/uui-symbol-expand": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.7.0.tgz", - "integrity": "sha512-vRMz1eDqogVqsuRlzzwq+F2SoXxUoquQ9DqBJPif1LO1LgRUZ3G/j1XyOR+CaMRiPEbu0olyNBHOt15dFbgqhA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.7.0.tgz", - "integrity": "sha512-//nk4+w55eB+EI3hP3O+2RWKg+gXuwKqfcIjEZiP6Nn2epA2XQUV7K5NmcUwKStPyPh9NCz2+EtSvNqJZaaKhA==", + "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==", "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.7.0.tgz", - "integrity": "sha512-wlvpchoIrD+HQJw5fNrxQ4UP2iSfYss+uJwcxDnoQLvLHR8KyS9jdZVCUe1ozMe5KAJ7w1Tw+qEIiXumMFTUAA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.7.0.tgz", - "integrity": "sha512-xuRXkAWlqAq2eO8VthT4JfOvVwpLeDwQwPOqwz4K50lR/6QHQAZdObG0g0DJuhlvehMMXPXrRneWZrAOWeIYGw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.7.0.tgz", - "integrity": "sha512-quMmD9iKg4EqV7JKs7k3pcAnxn/RGQjlXgIMrTAUbZbMclLAtTQrowij7ydX5rAdkPgtpQAWRmRuUTcufse64g==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.11.0" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.7.0.tgz", - "integrity": "sha512-QJg36PvN5LIHcl+fmcuhMFrkrTc5FDuj5L9DRStB/8V//HMhOKwjhOPcmc6xsxXm26R+jnS/7R67r/9PyjjhsQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-symbol-file": "1.7.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.7.0", - "@umbraco-ui/uui-symbol-folder": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.7.0.tgz", - "integrity": "sha512-gHNCYq/kwa7hXLHLKGBYrub8jTJHup7hf+mBf3g1LjipS+8M2a9tdpoO8yWzyEauzFsS4YJo45XqN6SRC1f2MQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.7.0.tgz", - "integrity": "sha512-gorUH9jCcCPdlDUy41xD6+4PQyZEL+9H3rnnKGg4xGQRw1+RnLCgmGa7mYiLfj1ycgi8l7MU50zCsQyNvPAPgg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-form-validation-message": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-form-validation-message": "1.11.0" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.7.0.tgz", - "integrity": "sha512-CU2ykzuIA3153EYKkRsqZ0SuGDxoy1zrdYVczWZ+sVxggyIWwazLMm5EZvdoiF8s3iP0m/v2LyyUh9GkBZ66LA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.7.0.tgz", - "integrity": "sha512-PtOSZkTxWskRrppdhxf17D+d54OylvtjE7muyLb2eJEYoP7KEaWdJ8Lfei5LtaUCRJlstFwQrCh/QbtWhe8Dfw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.7.0.tgz", - "integrity": "sha512-hG3VlF5VLt2XaNYHRUdqs2m5F4s9FUS4WxMc/TRu9Dzhqtie3A7UZ23qtONAcTCSPUxEXW5t809JUyxFi8kpBg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon": "1.11.0" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.7.0.tgz", - "integrity": "sha512-zgNKwT5L8Ez1R9WUO+vFRPbaUHHoSc6ohOfLA790WCA+F2krzbc7z3hNk6fHkFTR73K4rCaMu6gRbDX/PvuD8w==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry": "1.11.0" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.7.0.tgz", - "integrity": "sha512-c99s0hoggDTWFb3cq0uVcZcHCmstK82tVFJ4yPpaTMjJsilVCg9JnXE1B4tHvT25ZyAvN/pjJ/SYvLmKtU/MZA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.11.0.tgz", + "integrity": "sha512-DWe25cOCtYvRgqShL/UL4OnTRSbIZgTLp1JSdzLzSFxNm3PO2mAhYZuOMdGCjDkjv0G2lszmaqd7Ix8Xw+51ZA==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.7.1.tgz", - "integrity": "sha512-bzakMaaE1iSR7ioIzp2TGoBbwmBM+k712+0x+sK2dnQswRlLHL/Y95e7Byp/Aq6fNPayIwP6FaotB72JADDTig==", + "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==", "dependencies": { - "@umbraco-ui/uui-action-bar": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-file-dropzone": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.7.1.tgz", - "integrity": "sha512-kfHYiX9844/yE2yIwgk/e73BXiFYi5qn/aCJiy9T3lO6DEFaaHOJUccMyWsNIvSiPHYRX/11Mm0sP30jotjgGQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-input": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.7.0.tgz", - "integrity": "sha512-IU7/obNqFaHfuAyga8/wXC26+nqUEaovw67SeA83+2VUSyE7FeNTwW+AV7WFU7ZxeMYUvdJyxIpY43fClFg97A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.11.0.tgz", + "integrity": "sha512-doilXxlrc8v6BUtXUhlrno2aQSzlApUw1B9nnG2TuFOxoJ3iynJV6p6CcwPNlNPEYzPeiHFOaizPeDaZWZYmRg==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0", - "@umbraco-ui/uui-input": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0", + "@umbraco-ui/uui-input": "1.11.0" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.7.0.tgz", - "integrity": "sha512-PJmHNDCTiif89zkLUbBCdlnjY87TkqDfYQVjmhNwaO0DPxpQDh8gG2TvwD3Wp+aqdoVjR8FPIQH5pst+ulBa4g==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.11.0.tgz", + "integrity": "sha512-wRhfCnjjmZzs2gVoF1gZXNvIooPH8Qytr7UE6ijr6rDWbkDsltjhHocsPpcBAu1LUhqmqmlXDPHOOnc4sraL4A==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.7.0.tgz", - "integrity": "sha512-uk1m3wux4dNb/0AqSGslODLo6yVT9aXKBYcHTsvW2P0NQI8IImiJVWw9TWmNrfuBPACJhEqo3pVvfe/PCfsWzQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.11.0.tgz", + "integrity": "sha512-xeVOm9gGyPERnmwjmBNiqsfHFU4ROn6wGIEg6bV/CRdz0sjOKBHMYjdH+hg00kRQjj8oYt52HK4dVos8lDDYZg==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.7.0.tgz", - "integrity": "sha512-RKKThaEF1jqG+iU/vwH91QfXxaRvO10hABEReUj6IJYiU0sVCHxmZJczXnJFZKbl5pyEycOznV//b66J5kUddw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.7.0.tgz", - "integrity": "sha512-9lDRavgADrcQss5mbdmBrorzgSFNBr4srDA3B6PCya9pFpGdu/NgvQr/SrQzU0U2YSeW4jB88pyHwZaI6PCYug==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.7.0.tgz", - "integrity": "sha512-7/FqKCntviNUS8yzKhw4lYCWj598gYbzxBRvGJxVPinMOfAgMa8MAOGKpi7VDFHsqfHASfDCzHkqdywq0ku3nQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.7.0.tgz", - "integrity": "sha512-RTsrBmD1zjcP7XGPIGsxfBfOH+u4k3Jtw1qy/bxD1XLNH3ggOtfpQrpMzn/kxaer/wxYrUnXoDZDRjRuhHwvbg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-loader-bar": "1.7.0", - "@umbraco-ui/uui-symbol-expand": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-loader-bar": "1.11.0", + "@umbraco-ui/uui-symbol-expand": "1.11.0" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.7.0.tgz", - "integrity": "sha512-/XTu5kbPAgkbMrm1MISz+hvvEh3d2guBl7bs5EhiLBsq4XqnaDQwh15joS4wub5R2lfaodvJg7Or2VvFV+v5ug==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.11.0.tgz", + "integrity": "sha512-rNq8lhzKj4bk4EMgAIlnHcaQX0W7kQhHWBeJahvLL6jNMmiMGtN/ZtE0efG5tx1r4dixTPbiXXGAl8qMqgTIig==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.7.1.tgz", - "integrity": "sha512-T6oomUkqf6xFc4ZMGX4YHmeBDBLwSfkTz/9sksqTpFpiK86XGJMQ0yOfPhlWNZ9TrC4OJZDurZ/jnY1l97OxcQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-button-group": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-button": "1.11.0", + "@umbraco-ui/uui-button-group": "1.11.0" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.7.0.tgz", - "integrity": "sha512-aGG2AOXWfiRSo+0HAZkmZkWCXZTWyBB6mQ7+1XVcSHubsGLTimc6jcs+9y8c/OgMlFlm+YhDmp0bVSdmUKmYIg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.7.0.tgz", - "integrity": "sha512-az2Em1ZKaBLbPBKS3SePeCh6dk4NpdqsM+uRC5DFDLc95oAciKnC/gSjjZf1VtlL+hjb907R+nDQmszC9K7qfA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.11.0.tgz", + "integrity": "sha512-i90xXibf8BfP4Yd5Bp4wOfjnFEWQ2Qmn9vnDOWcxmsM9q7NQFx7m4287jJCMtfz2DUbj0VIFJlA2rffWnnSJzw==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.7.0.tgz", - "integrity": "sha512-LjoK+DbO6BcNBJXr6ZKUHTfXPf4ZeChCVDEf1YfsiyLGxoKgt605YqJ8t8OWLInlO3m1rZmB7f0Uxc58nnPjxg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.7.0.tgz", - "integrity": "sha512-dNuBdHKNVJUaeemA87uCNTBIeN6S+dLdgxGI2ayQNzA/71EDSdBlIMrdm8FTJ0H8Un/itvgaujhu7EHbckai3w==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.7.0.tgz", - "integrity": "sha512-3LV9H0HciGSMEpX1I7zSzmPssGvF+C907bl8hWnlmaVVKGirBjrKPHmeZQW/zpqRCtvDWipFYKOcgbKQzCA2Vw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.7.0.tgz", - "integrity": "sha512-/llhIEmVoJ4gb3LmOH1cfJ5zOSJry7TfJTxzruUpCxi+O68zMscgRZr+eh9DdF+Lz7zMbRxlubbVOZ58HhEPmQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.7.0.tgz", - "integrity": "sha512-BEb878VsSmRJuq1FCtoS9ryBvUErUfK8bQy93ErwgmesdUcuYpBJK1PfSe4x7SiLjD1vDlH9GHaWLyFiSJKfIQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.7.0.tgz", - "integrity": "sha512-yqTS6B3uA0e8g29+nqbUnyPncyRdeYGNR4mjA4gdL4iwPumBvC49tPoWds8Nq0lEyxJg9fLNMezokPOMs2fKvw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-ref": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon": "1.11.0", + "@umbraco-ui/uui-ref": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.7.0.tgz", - "integrity": "sha512-TmnpFGaG1QqUqvwlmXlXzpPZ+tCigqCxv4VVOYA9XwfUeqwoWmziQJ1jJyqdxSrHxRYkgg9Or8ZqObpKZ0HrCg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.7.0.tgz", - "integrity": "sha512-KiZWbggePxAmHWr31yJzWOrA4DLGMbw8goMSC49zinBX4X2FOqgOTG8dl4dCXMxN114wxcTDRFvdTcWpIOHeEQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.7.0.tgz", - "integrity": "sha512-FUZA7jjWOOA8HILRhD30mKO6NX0Hv+wL61gfIbWt95iGsmPwknth550Dm+i1Cc/3L63QmZD0qBQRTKRl7zfynA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.7.0.tgz", - "integrity": "sha512-PFXZzlPmJaNLrvCO3p9n5ViIBXfr7nJtm+3WphuUM6KiJMMa0Fv7au1CINv6abu+TzjBh6VcmoNdt8Hu2MfS7g==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.7.0.tgz", - "integrity": "sha512-OVvo+YDs0a3jqtm09XwaZdRNFwmDnSIBCTAllG+fLRbYQfwF0pCp96WOmuwQfGjlXhPrIjbhJ6YJH7R8QRUzbw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.7.0.tgz", - "integrity": "sha512-Z2qF53n9O7Ft/xgexY/lzUd8xeFusCLSnz7hkqfWgTIbSvdI9FXtMiqCWqD1nWmijIPYBKaqujBfibGtx1DcSg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.7.0.tgz", - "integrity": "sha512-W4rETai/KAyXkDRUn6h14S6PLigswzkE45ufHAc7K2QZGUgXikpntbE8UpsEfq1QdMQRTHDmjorGn2qT+C6ULA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.7.0.tgz", - "integrity": "sha512-pkPWTciiL9hPXpDO26wkkZFLze+jgL/xZkGgtrULrMRS5mJ6gan+8bB14iGtPt/ekFdgDmt6YcKozjp8g15xGg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.11.0.tgz", + "integrity": "sha512-slTOIvJZMMtCnVEhBVjAs1MPQBb1BAAa6R+DOoslC4aqA1yEgXWQmFu0xVZqiN0NTz3kqEF5zfexumVJ5f79LQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.7.0.tgz", - "integrity": "sha512-kP93yvwsvRUEyS4+PhkhwXpkWZUm77sKerB6Dte0Z579WMQclSAivy6va9kkj5zKxZYPcRbJ3H498FvfhxhMkw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.11.0.tgz", + "integrity": "sha512-sxWZCvznmTkpJ+VyoIjMRsVQuYC2SMnTWFd+7xrg3pk5SRySNxhZhyQUyf5jI1hAzrW9ATySDZlaRYCOMsC7uA==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.7.0.tgz", - "integrity": "sha512-Z9bv8uYU2+tQ3UoJM2Ymdpmey73bLBNuaIKJG1AOXi1c2CB1UHaIn0C0Cvj4eHLoIEVp29UZOpQM7ri3/zb7lg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.7.0.tgz", - "integrity": "sha512-m/vx7WnCbYw0cNqS7TM6JeS7S/AMEQlnVUOWa2w2GDIuKNy6Jb1bk0soW1B3Fi6Hc6Pq+pMeaKgVPIM5F7F4Cg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.7.0.tgz", - "integrity": "sha512-lyhROAhwbmgK24DerwTiP5iP8GeOiAcgbgkUfHhG8X6hWMx9nV3H1nJHil5jFAeXk9nbAzDw4UfUgQWeKePLTg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.7.0.tgz", - "integrity": "sha512-ZyS82vIqgqpPTx1atPaN+bw+Wr5e2lY2G9dpjTVx15PZtlI2Hp9aouiWyDRuCai8cc9Cj7n+7wF/K8QC7J8uCw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.7.0.tgz", - "integrity": "sha512-0EgbdXHY/aKniF0GZV6q64BWBsHK/dmar2hRNa/CpXHOGr04caY2svs44adWo4AOdGbPy9ayIglEzwSBRV+vXA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.7.0.tgz", - "integrity": "sha512-w+f3jvnVhkETiT3NERIsHJrYDZJC5zfihtW/KRE7isJflF8vrnEyUylv5ZJEih2kj0qCphoCswfMNQjwZbmMFQ==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.7.0.tgz", - "integrity": "sha512-mYG0BKW3F8quwsBRck3mhINDJrl+bmfTzQsQRBjjCtP/BuIlqb2JSZDn0KBK1Jj7gl2MJINgZSzsL89zjyRVHg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.7.0.tgz", - "integrity": "sha512-gE8KNPAKZbUkAf+ZYLWe0zK4TC914sNfoCZJY4v8aEJ8xkZ/mYXJ7FxVvE+gvYuZ033VqrO5Ko5AwWEXfw1iIA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.7.0.tgz", - "integrity": "sha512-9t9vdWOQ0NKg6aHTWqoIfAEK0M/DDrGkcn96FGvxxbPd+qkta4XDYCMEfOfMjGnGz+lukWoACectczEHXuI6gA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.11.0.tgz", + "integrity": "sha512-AXKMARK9WtyuU9T72LGprhBQXpYKw4rWGoGQwUjRk4lwdQD8WKeY3kfIIcaeabBiK5FPnZaEoCpxIkmPt77n2w==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.7.1.tgz", - "integrity": "sha512-HYX5abtHKEse8UC17bUJM0LV4Kt0MNVIV4I2PtOOMIbLFx8kIVL/bdi/IO5T8VzYtecLQI8dgELc0Y2wgRSvNA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.11.0.tgz", + "integrity": "sha512-IyB1qao2G3T5UNBj3Kw9EL7ikjAp8COvHVH8eTD+fjx1PbrNJmDl6utTV6tpysxLkT7UQ3o6QtjxstDtlUSqsg==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-popover-container": "1.7.0", - "@umbraco-ui/uui-symbol-more": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.7.0.tgz", - "integrity": "sha512-twrXe2U733r92ubBGXxWV9F5QP7SCJhKwYZbC2jbFOGoHpcxCtELvy36vEvgoWUF2BorPLQZSci7RHO0Hbnasw==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.7.0.tgz", - "integrity": "sha512-rMqd4h5U/hW/wRacbr6D7/MoK8gqgiLh341Q+CFTEAnWdXNvRakHe4DNspguDIYCPUTjjRshTJowj9ZdbxHO7w==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.11.0.tgz", + "integrity": "sha512-g4ciGte7YgHJkzhkLPn4xiGfjHXFbUWa86S4bg3WricucdF20EReLRc6I2jW7mo8lL+h+y8wLcIIQ8CquscLsQ==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.7.1.tgz", - "integrity": "sha512-SDAW0oYyboC5GvKg6GP0ZbNkr2C1qkVxSsO3gSAxI9+aUUbYuc3SijudyGCuESzdNshTbmya5OpUC3mnd5zdGA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-css": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.7.1.tgz", - "integrity": "sha512-m/B0XqBjAfEe30y2gHKJNbPxijF17zTU0VXb0sxTVa+1pb+eOtIMXVB6+DaYsr0TcsqPnq09kQruVEmvO8uWkg==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-toast-notification": "1.7.1" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-toast-notification": "1.11.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.7.0.tgz", - "integrity": "sha512-5edQz3E84q3dKCvqFhZoMYY8258m9rPXak6gnqtZyGhAzwx8qZ8r9TDTcXftBnW+EB7Th9DheCUZLrphs35ZlA==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.7.0.tgz", - "integrity": "sha512-1Rz7CyBy38IF926maF1fyNjLG/my/4oWQRl0/22h/Xr6SYj/wWNE/1u4rg2bW1HGSu9mNtiel4wd7tDJ4g30Ew==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.11.0.tgz", + "integrity": "sha512-ZWafhMLnR/Z55U4Nw2mUYiPOWrIcSYS4Oay388ZuEKZmfQ0iwGYGSBo4awn3OeY/mVoY88QY6R2siRq9jABKig==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-boolean-input": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-boolean-input": "1.11.0" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.7.0.tgz", - "integrity": "sha512-yPa1Z4S+ItjS+i9xgIobZ5QxfUyLRLguzqX8VARgCCxyoh5yXkoABhI9Fb0siSwc9TOtKuRaB+qQoV5rLnpu/g==", + "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==", "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@ungap/structured-clone": { @@ -11732,31 +11732,31 @@ } }, "node_modules/lit": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.2.tgz", - "integrity": "sha512-VZx5iAyMtX7CV4K8iTLdCkMaYZ7ipjJZ0JcSdJ0zIdGxxyurjIn7yuuSxNBD7QmjvcNJwr0JS4cAdAtsy7gZ6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz", + "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==", "peer": true, "dependencies": { "@lit/reactive-element": "^2.0.4", - "lit-element": "^4.0.4", - "lit-html": "^3.1.2" + "lit-element": "^4.1.0", + "lit-html": "^3.2.0" } }, "node_modules/lit-element": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.4.tgz", - "integrity": "sha512-98CvgulX6eCPs6TyAIQoJZBCQPo80rgXR+dVBs61cstJXqtI+USQZAbA4gFHh6L/mxBx9MrgPLHLsUgDUHAcCQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz", + "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==", "peer": true, "dependencies": { "@lit-labs/ssr-dom-shim": "^1.2.0", "@lit/reactive-element": "^2.0.4", - "lit-html": "^3.1.2" + "lit-html": "^3.2.0" } }, "node_modules/lit-html": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.2.tgz", - "integrity": "sha512-3OBZSUrPnAHoKJ9AMjRL/m01YJxQMf+TMHanNtTHG68ubjnZxK0RFl102DPzsw4mWnHibfZIBJm3LWCZ/LmMvg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz", + "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==", "peer": true, "dependencies": { "@types/trusted-types": "^2.0.2" diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 1c244f4899..ae65e8a6b8 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -19,8 +19,8 @@ }, "dependencies": { "@microsoft/signalr": "7.0.12", - "@umbraco-ui/uui": "1.7.1", - "@umbraco-ui/uui-css": "1.7.0", + "@umbraco-ui/uui": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0", "ace-builds": "1.31.1", "angular": "1.8.3", "angular-animate": "1.8.3", diff --git a/src/Umbraco.Web.UI.Login/package-lock.json b/src/Umbraco.Web.UI.Login/package-lock.json index b4524339e1..0e32d8cc14 100644 --- a/src/Umbraco.Web.UI.Login/package-lock.json +++ b/src/Umbraco.Web.UI.Login/package-lock.json @@ -11,8 +11,8 @@ "rxjs": "^7.8.1" }, "devDependencies": { - "@umbraco-ui/uui": "1.7.1", - "@umbraco-ui/uui-css": "1.7.0", + "@umbraco-ui/uui": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0", "typescript": "^5.3.3", "vite": "^5.1.7" }, @@ -730,897 +730,897 @@ "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==" }, "node_modules/@umbraco-ui/uui": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.7.1.tgz", - "integrity": "sha512-wHGMW8NQaWJTdbbb7r03sah2Esab4Iy8bFWaTU+UtnrOpNsZclPwxZ3kZcjHnFu32xDFFBF0+iQiCki8Uy4dkA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.11.0.tgz", + "integrity": "sha512-1mX7adcpAZRswPA1p64kqE83Rg5cbZsYM/b/OyUcObaL2cIuBCVvjjuUjgkL2el993GptIzl05XVocdj1dDCeQ==", "dev": true, "dependencies": { - "@umbraco-ui/uui-action-bar": "1.7.0", - "@umbraco-ui/uui-avatar": "1.7.0", - "@umbraco-ui/uui-avatar-group": "1.7.0", - "@umbraco-ui/uui-badge": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-boolean-input": "1.7.0", - "@umbraco-ui/uui-box": "1.7.0", - "@umbraco-ui/uui-breadcrumbs": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-button-group": "1.7.0", - "@umbraco-ui/uui-button-inline-create": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0", - "@umbraco-ui/uui-card-block-type": "1.7.0", - "@umbraco-ui/uui-card-content-node": "1.7.0", - "@umbraco-ui/uui-card-media": "1.7.0", - "@umbraco-ui/uui-card-user": "1.7.0", - "@umbraco-ui/uui-caret": "1.7.0", - "@umbraco-ui/uui-checkbox": "1.7.0", - "@umbraco-ui/uui-color-area": "1.7.0", - "@umbraco-ui/uui-color-picker": "1.7.0", - "@umbraco-ui/uui-color-slider": "1.7.0", - "@umbraco-ui/uui-color-swatch": "1.7.0", - "@umbraco-ui/uui-color-swatches": "1.7.0", - "@umbraco-ui/uui-combobox": "1.7.1", - "@umbraco-ui/uui-combobox-list": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0", - "@umbraco-ui/uui-dialog": "1.7.0", - "@umbraco-ui/uui-dialog-layout": "1.7.0", - "@umbraco-ui/uui-file-dropzone": "1.7.0", - "@umbraco-ui/uui-file-preview": "1.7.0", - "@umbraco-ui/uui-form": "1.7.0", - "@umbraco-ui/uui-form-layout-item": "1.7.0", - "@umbraco-ui/uui-form-validation-message": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-icon-registry": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0", - "@umbraco-ui/uui-input": "1.7.0", - "@umbraco-ui/uui-input-file": "1.7.1", - "@umbraco-ui/uui-input-lock": "1.7.1", - "@umbraco-ui/uui-input-password": "1.7.0", - "@umbraco-ui/uui-keyboard-shortcut": "1.7.0", - "@umbraco-ui/uui-label": "1.7.0", - "@umbraco-ui/uui-loader": "1.7.0", - "@umbraco-ui/uui-loader-bar": "1.7.0", - "@umbraco-ui/uui-loader-circle": "1.7.0", - "@umbraco-ui/uui-menu-item": "1.7.0", - "@umbraco-ui/uui-modal": "1.7.0", - "@umbraco-ui/uui-pagination": "1.7.1", - "@umbraco-ui/uui-popover": "1.7.0", - "@umbraco-ui/uui-popover-container": "1.7.0", - "@umbraco-ui/uui-progress-bar": "1.7.0", - "@umbraco-ui/uui-radio": "1.7.0", - "@umbraco-ui/uui-range-slider": "1.7.0", - "@umbraco-ui/uui-ref": "1.7.0", - "@umbraco-ui/uui-ref-list": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0", - "@umbraco-ui/uui-ref-node-data-type": "1.7.0", - "@umbraco-ui/uui-ref-node-document-type": "1.7.0", - "@umbraco-ui/uui-ref-node-form": "1.7.0", - "@umbraco-ui/uui-ref-node-member": "1.7.0", - "@umbraco-ui/uui-ref-node-package": "1.7.0", - "@umbraco-ui/uui-ref-node-user": "1.7.0", - "@umbraco-ui/uui-scroll-container": "1.7.0", - "@umbraco-ui/uui-select": "1.7.0", - "@umbraco-ui/uui-slider": "1.7.0", - "@umbraco-ui/uui-symbol-expand": "1.7.0", - "@umbraco-ui/uui-symbol-file": "1.7.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.7.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.7.0", - "@umbraco-ui/uui-symbol-folder": "1.7.0", - "@umbraco-ui/uui-symbol-lock": "1.7.0", - "@umbraco-ui/uui-symbol-more": "1.7.0", - "@umbraco-ui/uui-symbol-sort": "1.7.0", - "@umbraco-ui/uui-table": "1.7.0", - "@umbraco-ui/uui-tabs": "1.7.1", - "@umbraco-ui/uui-tag": "1.7.0", - "@umbraco-ui/uui-textarea": "1.7.0", - "@umbraco-ui/uui-toast-notification": "1.7.1", - "@umbraco-ui/uui-toast-notification-container": "1.7.1", - "@umbraco-ui/uui-toast-notification-layout": "1.7.0", - "@umbraco-ui/uui-toggle": "1.7.0", - "@umbraco-ui/uui-visually-hidden": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-action-bar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.7.0.tgz", - "integrity": "sha512-Lw067iEU4DihiOsL3cg2QqE4x7B7bqjYQK0EouBbD+mhJaE2IOw5eve2UIBN1KU/iQ+7V9q4qa++is1nitvUWA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button-group": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-button-group": "1.11.0" } }, "node_modules/@umbraco-ui/uui-avatar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.7.0.tgz", - "integrity": "sha512-cW3qTTarFqXK4Ze5xMERo9pj3pRRKTvTDB57a5uA0gQ1/70uhgPnozWSX7EK22ml4w/5pmtxXXgRKfSiU9DGtQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-avatar-group": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.7.0.tgz", - "integrity": "sha512-TFDR0Mb+ug1NzVXq9RnxMiQ9pcxBcmzfOoxpR1NWMB/sAgNs/H/pTTqjieLel0/A5Am9q//8f7f9vmhTPpybGg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-avatar": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-avatar": "1.11.0", + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-badge": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.7.0.tgz", - "integrity": "sha512-cdPHjXMag8KkYLaWfyYfp9N1qqG+th2Ijx7ms8EpTHAX2gtU1L/A3ShxWox0Ck1TJ75jrW66+HrqiMwDOmbn6g==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.11.0.tgz", + "integrity": "sha512-7VMZzUZ+CYaFhsCe8XS8VgadBhXZtJh0ilZ695YG9Q9IAbAVyeART59VwRzO/1kS0hfCj10DPEKp8IPMbePWEA==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-base": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.7.0.tgz", - "integrity": "sha512-66aDdgTrq2nx4BNzM9A/lc9dZYz/fyX5OVpkQDRsrpYeOLJMN3oOnE7aChIdBNW3I9lfVNJf6fh0iL27K5JCiQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.11.0.tgz", + "integrity": "sha512-w7HQDNtEt0qnu+psrwxvrdNxUT08qZ1fYygqH9yeKFyE5GMDvYlL1TWU696Lo72LTbTdSMm/ka9b2QBJv1ZJxA==", "dev": true, "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-boolean-input": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.7.0.tgz", - "integrity": "sha512-Hp6wOFqFLaZU0oW63GlMJ8s4va/TG+i7Sjs0qT91//5iJhJtpvgwY3j4ARoDfk0d8rKRIapiPT+hNMo9xr1sfQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-box": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.7.0.tgz", - "integrity": "sha512-1P13tsVJXPEpMiHrw1FmsM0dvCLce8DevZAcP1ArDwtqWrwdArR2eRwlhVEZYu2MJkR2tESE3XGTaSOWHyC8og==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.11.0.tgz", + "integrity": "sha512-gYiERouKMpFy/n/6LDh9ckzWpUa2vBmCsWS41Gskct3WZNSVdApZ3g2yvE9ZoJoJB2Q26JfbKShuw0BaJkEFxg==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0" } }, "node_modules/@umbraco-ui/uui-breadcrumbs": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.7.0.tgz", - "integrity": "sha512-y0sB4UypNwCle9qPlQ7Y++a4BkmFpn9vSTeJ6WRWueVyjKT99icmCV1c8/Q47blMajp0FLG2/ajevxg/aZSO4Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.11.0.tgz", + "integrity": "sha512-wRTtuZAKb0z2Mi3P3wb1CcIO1ExnnFD8vCsHxiTEAjb2e2VzEaEwnnugHnr8chxlOKiTPyX8NtsBXDLTnL/TRA==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-button": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.7.1.tgz", - "integrity": "sha512-z2nZccn/Hel2QvytWVejDzqjNPRLJ/jLgCmLpgHoKU2IlckEgZqy4wxKcgH2Iu2bJ+wgIwpAAmlidLK0LX0tCw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0" } }, "node_modules/@umbraco-ui/uui-button-group": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.7.0.tgz", - "integrity": "sha512-CM0sytzzEXiDmFfB6GXnmQw5LzCNuwSo66BC6zYI4cg1+mk2a1UBu1Z8CVpvS3tsTkzk/nGd/ZFKkoIziDVKJg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-button-inline-create": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.7.0.tgz", - "integrity": "sha512-SVep/tcsTJuO8jvZIX0e3EOaY1S+sOk0ZFmq+HxUJDt6csFjXsqJO48DjIon1AKq95ATTM9Iqs/hPSDVHrqNvw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.7.0.tgz", - "integrity": "sha512-BBWJ62UH1dmcHvZ3H0fRCnM9c+ebQMNaZlGDDWP5lPfv+2KjXXkLRuj6tPUthHa53e5Rf6AAKjKsjRssM4dsLQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card-block-type": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.7.0.tgz", - "integrity": "sha512-SrLgooo2imluSV8S3bp+0kA2K7zuMDAXZTuzQJRf2hzq208In65D5rBxn8OcEJsGD3lHMp6+w8rg8Ol5NlEbXA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-card": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card-content-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.7.0.tgz", - "integrity": "sha512-wkb9BaUfuZkrMczsm1q4vuP0zSOp0gfiiiXCxFRDNmWJc3jKiL3zF619PzviEZrz10/f7WRnA7MLfDgsAmQpAQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-card": "1.11.0", + "@umbraco-ui/uui-icon": "1.11.0" } }, "node_modules/@umbraco-ui/uui-card-media": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.7.0.tgz", - "integrity": "sha512-Jwli2j//U1v4zG5fvkrekduf3qCa5w0RNP28RBxeqqQKzO8B5UpWqIP86/qaV7hvlp/ZuTCYrdkeWLgUV85tBg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.11.0.tgz", + "integrity": "sha512-uOdN0iu5OKsOtxhTSE8epuUMo2iXq6FEVqBPQBHAmAFELDFyNf2UBwnBxnrTuU6RJ0jbGuLTqQQM7Gv8vD6Kjg==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0", - "@umbraco-ui/uui-symbol-file": "1.7.0", - "@umbraco-ui/uui-symbol-folder": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-card-user": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.7.0.tgz", - "integrity": "sha512-4fBXEICxi4ICAM2wn40DrUV1pPGSDFJmzacOA1PXxb1pzQjxw/hb/hnu96xqjHscX+bUAWnWHkb60RnrWmmcsg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-avatar": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-card": "1.7.0" + "@umbraco-ui/uui-avatar": "1.11.0", + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-card": "1.11.0" } }, "node_modules/@umbraco-ui/uui-caret": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.7.0.tgz", - "integrity": "sha512-sVWUQGaLCAwhFH5mmE83+cwDjTyZamRWHgmVakTac2L9qYkwhTwzRgIol1t4i0DQMDFd4oLZ1zq+ysWvAOCmmQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-checkbox": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.7.0.tgz", - "integrity": "sha512-7bY8FgSEscGtMYf0EtvmU4XuchV8bdjs+gwBKCVYogAELDdKbCTxWI6/HRqR6wDUOltpP1okFYN6rISugkUKtw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.11.0.tgz", + "integrity": "sha512-bOfJXJ5LMiGCMD37A3mzYjiGTIvzjREN2AhtqGLbwcrAgj662WVhw0aQobo2+iIwaMUIAvl3kNS8930XDeUe/A==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-boolean-input": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-boolean-input": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0" } }, "node_modules/@umbraco-ui/uui-color-area": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.7.0.tgz", - "integrity": "sha512-7FashEB3hoh9p833gEhseq1t2mICVzb5zRe+FJ+vKFnTI2uuIRLjwD0pqSwmVAxoFCPgb81B6V5yH/pSrrzZEQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", + "@umbraco-ui/uui-base": "1.11.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-picker": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.7.0.tgz", - "integrity": "sha512-9JYlgg6i/gxwTIYsCJ8QnSjhZzZjJBiu2HZwDJ/2rm8uy/jNmbCf5aK+WHR7RbwMGNrF4/X/58t5woBzwSMUIA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-popover-container": "1.7.0", + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-popover-container": "1.11.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-slider": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.7.0.tgz", - "integrity": "sha512-DVyYeZsBG35430Cay6Dv8oO7dvi+aow6fVAJDHA4+CXdOSet4RTLO3oc1i51JwDmBiBhtLKGfo/wflrFxyfr0w==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-color-swatch": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.7.0.tgz", - "integrity": "sha512-hp4Oicv7eLMvSn6jUerjDkYY6R/8JCRxbXabfbfZOZ/YwocSLN6DBc0nxlb/W8IETy26VCEFXH+tYKvZbsAB2Q==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.11.0.tgz", + "integrity": "sha512-BeCyW9FyVmjE2W8u3k5bPwkRUIVbudK2q9VTKmIcnkwsZz8wv6dDpFoFb92So8YSzMhdiVIRQ14fnphHwMHfWQ==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0", + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0", "colord": "^2.9.3" } }, "node_modules/@umbraco-ui/uui-color-swatches": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.7.0.tgz", - "integrity": "sha512-XIPP4BhaRB6nL3HAt2KRsEeslq/I2hMl8eQzgbz8y9V6yf7uq8q0OCMqQy2XB6bQ48N+sOqXfjKLPIT4yTIH7A==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-color-swatch": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-color-swatch": "1.11.0" } }, "node_modules/@umbraco-ui/uui-combobox": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.7.1.tgz", - "integrity": "sha512-4nbsRyqJO+rifoug+1PlWA8oI1L6f3aj7P/p9UT4pgcT8mpJ5Fv70XaFXMPEaCWh8HgZLsvMKDClXNzHXlvcLA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-combobox-list": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-popover-container": "1.7.0", - "@umbraco-ui/uui-scroll-container": "1.7.0", - "@umbraco-ui/uui-symbol-expand": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-combobox-list": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.7.0.tgz", - "integrity": "sha512-vRMz1eDqogVqsuRlzzwq+F2SoXxUoquQ9DqBJPif1LO1LgRUZ3G/j1XyOR+CaMRiPEbu0olyNBHOt15dFbgqhA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-css": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.7.0.tgz", - "integrity": "sha512-//nk4+w55eB+EI3hP3O+2RWKg+gXuwKqfcIjEZiP6Nn2epA2XQUV7K5NmcUwKStPyPh9NCz2+EtSvNqJZaaKhA==", + "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==", "dev": true, "peerDependencies": { "lit": ">=2.8.0" } }, "node_modules/@umbraco-ui/uui-dialog": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.7.0.tgz", - "integrity": "sha512-wlvpchoIrD+HQJw5fNrxQ4UP2iSfYss+uJwcxDnoQLvLHR8KyS9jdZVCUe1ozMe5KAJ7w1Tw+qEIiXumMFTUAA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0" } }, "node_modules/@umbraco-ui/uui-dialog-layout": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.7.0.tgz", - "integrity": "sha512-xuRXkAWlqAq2eO8VthT4JfOvVwpLeDwQwPOqwz4K50lR/6QHQAZdObG0g0DJuhlvehMMXPXrRneWZrAOWeIYGw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-file-dropzone": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.7.0.tgz", - "integrity": "sha512-quMmD9iKg4EqV7JKs7k3pcAnxn/RGQjlXgIMrTAUbZbMclLAtTQrowij7ydX5rAdkPgtpQAWRmRuUTcufse64g==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-symbol-file-dropzone": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.11.0" } }, "node_modules/@umbraco-ui/uui-file-preview": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.7.0.tgz", - "integrity": "sha512-QJg36PvN5LIHcl+fmcuhMFrkrTc5FDuj5L9DRStB/8V//HMhOKwjhOPcmc6xsxXm26R+jnS/7R67r/9PyjjhsQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-symbol-file": "1.7.0", - "@umbraco-ui/uui-symbol-file-thumbnail": "1.7.0", - "@umbraco-ui/uui-symbol-folder": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-form": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.7.0.tgz", - "integrity": "sha512-gHNCYq/kwa7hXLHLKGBYrub8jTJHup7hf+mBf3g1LjipS+8M2a9tdpoO8yWzyEauzFsS4YJo45XqN6SRC1f2MQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-form-layout-item": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.7.0.tgz", - "integrity": "sha512-gorUH9jCcCPdlDUy41xD6+4PQyZEL+9H3rnnKGg4xGQRw1+RnLCgmGa7mYiLfj1ycgi8l7MU50zCsQyNvPAPgg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-form-validation-message": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-form-validation-message": "1.11.0" } }, "node_modules/@umbraco-ui/uui-form-validation-message": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.7.0.tgz", - "integrity": "sha512-CU2ykzuIA3153EYKkRsqZ0SuGDxoy1zrdYVczWZ+sVxggyIWwazLMm5EZvdoiF8s3iP0m/v2LyyUh9GkBZ66LA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-icon": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.7.0.tgz", - "integrity": "sha512-PtOSZkTxWskRrppdhxf17D+d54OylvtjE7muyLb2eJEYoP7KEaWdJ8Lfei5LtaUCRJlstFwQrCh/QbtWhe8Dfw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-icon-registry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.7.0.tgz", - "integrity": "sha512-hG3VlF5VLt2XaNYHRUdqs2m5F4s9FUS4WxMc/TRu9Dzhqtie3A7UZ23qtONAcTCSPUxEXW5t809JUyxFi8kpBg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon": "1.11.0" } }, "node_modules/@umbraco-ui/uui-icon-registry-essential": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.7.0.tgz", - "integrity": "sha512-zgNKwT5L8Ez1R9WUO+vFRPbaUHHoSc6ohOfLA790WCA+F2krzbc7z3hNk6fHkFTR73K4rCaMu6gRbDX/PvuD8w==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry": "1.11.0" } }, "node_modules/@umbraco-ui/uui-input": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.7.0.tgz", - "integrity": "sha512-c99s0hoggDTWFb3cq0uVcZcHCmstK82tVFJ4yPpaTMjJsilVCg9JnXE1B4tHvT25ZyAvN/pjJ/SYvLmKtU/MZA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.11.0.tgz", + "integrity": "sha512-DWe25cOCtYvRgqShL/UL4OnTRSbIZgTLp1JSdzLzSFxNm3PO2mAhYZuOMdGCjDkjv0G2lszmaqd7Ix8Xw+51ZA==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-input-file": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.7.1.tgz", - "integrity": "sha512-bzakMaaE1iSR7ioIzp2TGoBbwmBM+k712+0x+sK2dnQswRlLHL/Y95e7Byp/Aq6fNPayIwP6FaotB72JADDTig==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-action-bar": "1.7.0", - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-file-dropzone": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-input-lock": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.7.1.tgz", - "integrity": "sha512-kfHYiX9844/yE2yIwgk/e73BXiFYi5qn/aCJiy9T3lO6DEFaaHOJUccMyWsNIvSiPHYRX/11Mm0sP30jotjgGQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-input": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-input-password": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.7.0.tgz", - "integrity": "sha512-IU7/obNqFaHfuAyga8/wXC26+nqUEaovw67SeA83+2VUSyE7FeNTwW+AV7WFU7ZxeMYUvdJyxIpY43fClFg97A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.11.0.tgz", + "integrity": "sha512-doilXxlrc8v6BUtXUhlrno2aQSzlApUw1B9nnG2TuFOxoJ3iynJV6p6CcwPNlNPEYzPeiHFOaizPeDaZWZYmRg==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0", - "@umbraco-ui/uui-input": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon-registry-essential": "1.11.0", + "@umbraco-ui/uui-input": "1.11.0" } }, "node_modules/@umbraco-ui/uui-keyboard-shortcut": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.7.0.tgz", - "integrity": "sha512-PJmHNDCTiif89zkLUbBCdlnjY87TkqDfYQVjmhNwaO0DPxpQDh8gG2TvwD3Wp+aqdoVjR8FPIQH5pst+ulBa4g==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.11.0.tgz", + "integrity": "sha512-wRhfCnjjmZzs2gVoF1gZXNvIooPH8Qytr7UE6ijr6rDWbkDsltjhHocsPpcBAu1LUhqmqmlXDPHOOnc4sraL4A==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-label": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.7.0.tgz", - "integrity": "sha512-uk1m3wux4dNb/0AqSGslODLo6yVT9aXKBYcHTsvW2P0NQI8IImiJVWw9TWmNrfuBPACJhEqo3pVvfe/PCfsWzQ==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.11.0.tgz", + "integrity": "sha512-xeVOm9gGyPERnmwjmBNiqsfHFU4ROn6wGIEg6bV/CRdz0sjOKBHMYjdH+hg00kRQjj8oYt52HK4dVos8lDDYZg==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-loader": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.7.0.tgz", - "integrity": "sha512-RKKThaEF1jqG+iU/vwH91QfXxaRvO10hABEReUj6IJYiU0sVCHxmZJczXnJFZKbl5pyEycOznV//b66J5kUddw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-loader-bar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.7.0.tgz", - "integrity": "sha512-9lDRavgADrcQss5mbdmBrorzgSFNBr4srDA3B6PCya9pFpGdu/NgvQr/SrQzU0U2YSeW4jB88pyHwZaI6PCYug==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-loader-circle": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.7.0.tgz", - "integrity": "sha512-7/FqKCntviNUS8yzKhw4lYCWj598gYbzxBRvGJxVPinMOfAgMa8MAOGKpi7VDFHsqfHASfDCzHkqdywq0ku3nQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-menu-item": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.7.0.tgz", - "integrity": "sha512-RTsrBmD1zjcP7XGPIGsxfBfOH+u4k3Jtw1qy/bxD1XLNH3ggOtfpQrpMzn/kxaer/wxYrUnXoDZDRjRuhHwvbg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-loader-bar": "1.7.0", - "@umbraco-ui/uui-symbol-expand": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-loader-bar": "1.11.0", + "@umbraco-ui/uui-symbol-expand": "1.11.0" } }, "node_modules/@umbraco-ui/uui-modal": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.7.0.tgz", - "integrity": "sha512-/XTu5kbPAgkbMrm1MISz+hvvEh3d2guBl7bs5EhiLBsq4XqnaDQwh15joS4wub5R2lfaodvJg7Or2VvFV+v5ug==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.11.0.tgz", + "integrity": "sha512-rNq8lhzKj4bk4EMgAIlnHcaQX0W7kQhHWBeJahvLL6jNMmiMGtN/ZtE0efG5tx1r4dixTPbiXXGAl8qMqgTIig==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-pagination": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.7.1.tgz", - "integrity": "sha512-T6oomUkqf6xFc4ZMGX4YHmeBDBLwSfkTz/9sksqTpFpiK86XGJMQ0yOfPhlWNZ9TrC4OJZDurZ/jnY1l97OxcQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-button-group": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-button": "1.11.0", + "@umbraco-ui/uui-button-group": "1.11.0" } }, "node_modules/@umbraco-ui/uui-popover": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.7.0.tgz", - "integrity": "sha512-aGG2AOXWfiRSo+0HAZkmZkWCXZTWyBB6mQ7+1XVcSHubsGLTimc6jcs+9y8c/OgMlFlm+YhDmp0bVSdmUKmYIg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-popover-container": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.7.0.tgz", - "integrity": "sha512-az2Em1ZKaBLbPBKS3SePeCh6dk4NpdqsM+uRC5DFDLc95oAciKnC/gSjjZf1VtlL+hjb907R+nDQmszC9K7qfA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.11.0.tgz", + "integrity": "sha512-i90xXibf8BfP4Yd5Bp4wOfjnFEWQ2Qmn9vnDOWcxmsM9q7NQFx7m4287jJCMtfz2DUbj0VIFJlA2rffWnnSJzw==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-progress-bar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.7.0.tgz", - "integrity": "sha512-LjoK+DbO6BcNBJXr6ZKUHTfXPf4ZeChCVDEf1YfsiyLGxoKgt605YqJ8t8OWLInlO3m1rZmB7f0Uxc58nnPjxg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-radio": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.7.0.tgz", - "integrity": "sha512-dNuBdHKNVJUaeemA87uCNTBIeN6S+dLdgxGI2ayQNzA/71EDSdBlIMrdm8FTJ0H8Un/itvgaujhu7EHbckai3w==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-range-slider": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.7.0.tgz", - "integrity": "sha512-3LV9H0HciGSMEpX1I7zSzmPssGvF+C907bl8hWnlmaVVKGirBjrKPHmeZQW/zpqRCtvDWipFYKOcgbKQzCA2Vw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.7.0.tgz", - "integrity": "sha512-/llhIEmVoJ4gb3LmOH1cfJ5zOSJry7TfJTxzruUpCxi+O68zMscgRZr+eh9DdF+Lz7zMbRxlubbVOZ58HhEPmQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-list": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.7.0.tgz", - "integrity": "sha512-BEb878VsSmRJuq1FCtoS9ryBvUErUfK8bQy93ErwgmesdUcuYpBJK1PfSe4x7SiLjD1vDlH9GHaWLyFiSJKfIQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.7.0.tgz", - "integrity": "sha512-yqTS6B3uA0e8g29+nqbUnyPncyRdeYGNR4mjA4gdL4iwPumBvC49tPoWds8Nq0lEyxJg9fLNMezokPOMs2fKvw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-ref": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-icon": "1.11.0", + "@umbraco-ui/uui-ref": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-data-type": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.7.0.tgz", - "integrity": "sha512-TmnpFGaG1QqUqvwlmXlXzpPZ+tCigqCxv4VVOYA9XwfUeqwoWmziQJ1jJyqdxSrHxRYkgg9Or8ZqObpKZ0HrCg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-document-type": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.7.0.tgz", - "integrity": "sha512-KiZWbggePxAmHWr31yJzWOrA4DLGMbw8goMSC49zinBX4X2FOqgOTG8dl4dCXMxN114wxcTDRFvdTcWpIOHeEQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-form": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.7.0.tgz", - "integrity": "sha512-FUZA7jjWOOA8HILRhD30mKO6NX0Hv+wL61gfIbWt95iGsmPwknth550Dm+i1Cc/3L63QmZD0qBQRTKRl7zfynA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-member": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.7.0.tgz", - "integrity": "sha512-PFXZzlPmJaNLrvCO3p9n5ViIBXfr7nJtm+3WphuUM6KiJMMa0Fv7au1CINv6abu+TzjBh6VcmoNdt8Hu2MfS7g==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-package": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.7.0.tgz", - "integrity": "sha512-OVvo+YDs0a3jqtm09XwaZdRNFwmDnSIBCTAllG+fLRbYQfwF0pCp96WOmuwQfGjlXhPrIjbhJ6YJH7R8QRUzbw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-ref-node-user": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.7.0.tgz", - "integrity": "sha512-Z2qF53n9O7Ft/xgexY/lzUd8xeFusCLSnz7hkqfWgTIbSvdI9FXtMiqCWqD1nWmijIPYBKaqujBfibGtx1DcSg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-ref-node": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-ref-node": "1.11.0" } }, "node_modules/@umbraco-ui/uui-scroll-container": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.7.0.tgz", - "integrity": "sha512-W4rETai/KAyXkDRUn6h14S6PLigswzkE45ufHAc7K2QZGUgXikpntbE8UpsEfq1QdMQRTHDmjorGn2qT+C6ULA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-select": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.7.0.tgz", - "integrity": "sha512-pkPWTciiL9hPXpDO26wkkZFLze+jgL/xZkGgtrULrMRS5mJ6gan+8bB14iGtPt/ekFdgDmt6YcKozjp8g15xGg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.11.0.tgz", + "integrity": "sha512-slTOIvJZMMtCnVEhBVjAs1MPQBb1BAAa6R+DOoslC4aqA1yEgXWQmFu0xVZqiN0NTz3kqEF5zfexumVJ5f79LQ==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-slider": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.7.0.tgz", - "integrity": "sha512-kP93yvwsvRUEyS4+PhkhwXpkWZUm77sKerB6Dte0Z579WMQclSAivy6va9kkj5zKxZYPcRbJ3H498FvfhxhMkw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.11.0.tgz", + "integrity": "sha512-sxWZCvznmTkpJ+VyoIjMRsVQuYC2SMnTWFd+7xrg3pk5SRySNxhZhyQUyf5jI1hAzrW9ATySDZlaRYCOMsC7uA==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-expand": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.7.0.tgz", - "integrity": "sha512-Z9bv8uYU2+tQ3UoJM2Ymdpmey73bLBNuaIKJG1AOXi1c2CB1UHaIn0C0Cvj4eHLoIEVp29UZOpQM7ri3/zb7lg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-file": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.7.0.tgz", - "integrity": "sha512-m/vx7WnCbYw0cNqS7TM6JeS7S/AMEQlnVUOWa2w2GDIuKNy6Jb1bk0soW1B3Fi6Hc6Pq+pMeaKgVPIM5F7F4Cg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.7.0.tgz", - "integrity": "sha512-lyhROAhwbmgK24DerwTiP5iP8GeOiAcgbgkUfHhG8X6hWMx9nV3H1nJHil5jFAeXk9nbAzDw4UfUgQWeKePLTg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.7.0.tgz", - "integrity": "sha512-ZyS82vIqgqpPTx1atPaN+bw+Wr5e2lY2G9dpjTVx15PZtlI2Hp9aouiWyDRuCai8cc9Cj7n+7wF/K8QC7J8uCw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-folder": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.7.0.tgz", - "integrity": "sha512-0EgbdXHY/aKniF0GZV6q64BWBsHK/dmar2hRNa/CpXHOGr04caY2svs44adWo4AOdGbPy9ayIglEzwSBRV+vXA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-lock": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.7.0.tgz", - "integrity": "sha512-w+f3jvnVhkETiT3NERIsHJrYDZJC5zfihtW/KRE7isJflF8vrnEyUylv5ZJEih2kj0qCphoCswfMNQjwZbmMFQ==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-more": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.7.0.tgz", - "integrity": "sha512-mYG0BKW3F8quwsBRck3mhINDJrl+bmfTzQsQRBjjCtP/BuIlqb2JSZDn0KBK1Jj7gl2MJINgZSzsL89zjyRVHg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-symbol-sort": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.7.0.tgz", - "integrity": "sha512-gE8KNPAKZbUkAf+ZYLWe0zK4TC914sNfoCZJY4v8aEJ8xkZ/mYXJ7FxVvE+gvYuZ033VqrO5Ko5AwWEXfw1iIA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-table": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.7.0.tgz", - "integrity": "sha512-9t9vdWOQ0NKg6aHTWqoIfAEK0M/DDrGkcn96FGvxxbPd+qkta4XDYCMEfOfMjGnGz+lukWoACectczEHXuI6gA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.11.0.tgz", + "integrity": "sha512-AXKMARK9WtyuU9T72LGprhBQXpYKw4rWGoGQwUjRk4lwdQD8WKeY3kfIIcaeabBiK5FPnZaEoCpxIkmPt77n2w==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-tabs": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.7.1.tgz", - "integrity": "sha512-HYX5abtHKEse8UC17bUJM0LV4Kt0MNVIV4I2PtOOMIbLFx8kIVL/bdi/IO5T8VzYtecLQI8dgELc0Y2wgRSvNA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.11.0.tgz", + "integrity": "sha512-IyB1qao2G3T5UNBj3Kw9EL7ikjAp8COvHVH8eTD+fjx1PbrNJmDl6utTV6tpysxLkT7UQ3o6QtjxstDtlUSqsg==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-popover-container": "1.7.0", - "@umbraco-ui/uui-symbol-more": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-tag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.7.0.tgz", - "integrity": "sha512-twrXe2U733r92ubBGXxWV9F5QP7SCJhKwYZbC2jbFOGoHpcxCtELvy36vEvgoWUF2BorPLQZSci7RHO0Hbnasw==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-textarea": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.7.0.tgz", - "integrity": "sha512-rMqd4h5U/hW/wRacbr6D7/MoK8gqgiLh341Q+CFTEAnWdXNvRakHe4DNspguDIYCPUTjjRshTJowj9ZdbxHO7w==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.11.0.tgz", + "integrity": "sha512-g4ciGte7YgHJkzhkLPn4xiGfjHXFbUWa86S4bg3WricucdF20EReLRc6I2jW7mo8lL+h+y8wLcIIQ8CquscLsQ==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/@umbraco-ui/uui-toast-notification": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.7.1.tgz", - "integrity": "sha512-SDAW0oYyboC5GvKg6GP0ZbNkr2C1qkVxSsO3gSAxI9+aUUbYuc3SijudyGCuESzdNshTbmya5OpUC3mnd5zdGA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-button": "1.7.1", - "@umbraco-ui/uui-css": "1.7.0", - "@umbraco-ui/uui-icon": "1.7.0", - "@umbraco-ui/uui-icon-registry-essential": "1.7.0" + "@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" } }, "node_modules/@umbraco-ui/uui-toast-notification-container": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.7.1.tgz", - "integrity": "sha512-m/B0XqBjAfEe30y2gHKJNbPxijF17zTU0VXb0sxTVa+1pb+eOtIMXVB6+DaYsr0TcsqPnq09kQruVEmvO8uWkg==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-toast-notification": "1.7.1" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-toast-notification": "1.11.0" } }, "node_modules/@umbraco-ui/uui-toast-notification-layout": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.7.0.tgz", - "integrity": "sha512-5edQz3E84q3dKCvqFhZoMYY8258m9rPXak6gnqtZyGhAzwx8qZ8r9TDTcXftBnW+EB7Th9DheCUZLrphs35ZlA==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-css": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0" } }, "node_modules/@umbraco-ui/uui-toggle": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.7.0.tgz", - "integrity": "sha512-1Rz7CyBy38IF926maF1fyNjLG/my/4oWQRl0/22h/Xr6SYj/wWNE/1u4rg2bW1HGSu9mNtiel4wd7tDJ4g30Ew==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.11.0.tgz", + "integrity": "sha512-ZWafhMLnR/Z55U4Nw2mUYiPOWrIcSYS4Oay388ZuEKZmfQ0iwGYGSBo4awn3OeY/mVoY88QY6R2siRq9jABKig==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0", - "@umbraco-ui/uui-boolean-input": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0", + "@umbraco-ui/uui-boolean-input": "1.11.0" } }, "node_modules/@umbraco-ui/uui-visually-hidden": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.7.0.tgz", - "integrity": "sha512-yPa1Z4S+ItjS+i9xgIobZ5QxfUyLRLguzqX8VARgCCxyoh5yXkoABhI9Fb0siSwc9TOtKuRaB+qQoV5rLnpu/g==", + "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==", "dev": true, "dependencies": { - "@umbraco-ui/uui-base": "1.7.0" + "@umbraco-ui/uui-base": "1.11.0" } }, "node_modules/ansi-escapes": { diff --git a/src/Umbraco.Web.UI.Login/package.json b/src/Umbraco.Web.UI.Login/package.json index 90e3bf68cb..486e3e94eb 100644 --- a/src/Umbraco.Web.UI.Login/package.json +++ b/src/Umbraco.Web.UI.Login/package.json @@ -16,10 +16,10 @@ "lit": "^3.1.2", "msw": "^2.2.0", "rxjs": "^7.8.1" - }, + }, "devDependencies": { - "@umbraco-ui/uui": "1.7.1", - "@umbraco-ui/uui-css": "1.7.0", + "@umbraco-ui/uui": "1.11.0", + "@umbraco-ui/uui-css": "1.11.0", "typescript": "^5.3.3", "vite": "^5.1.7" }, diff --git a/src/Umbraco.Web.UI.Login/src/auth-styles.css b/src/Umbraco.Web.UI.Login/src/auth-styles.css index 8e5b5efd53..f63dfa92e6 100644 --- a/src/Umbraco.Web.UI.Login/src/auth-styles.css +++ b/src/Umbraco.Web.UI.Login/src/auth-styles.css @@ -1,24 +1,12 @@ -#umb-login-form umb-login-input { +#umb-login-form input { width: 100%; height: var(--input-height); box-sizing: border-box; display: block; border: 1px solid var(--uui-color-border); border-radius: var(--uui-border-radius); - outline: none; background-color: var(--uui-color-surface); -} - -#umb-login-form umb-login-input input { - width: 100%; - height: 100%; - display: block; - box-sizing: border-box; - border: none; - outline: none; - background: none; padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px); - font-size: inherit; } #umb-login-form uui-form-layout-item { @@ -26,11 +14,31 @@ margin-bottom: var(--uui-size-space-4); } -#umb-login-form umb-login-input:focus-within { +#umb-login-form input:focus-within { border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1)); outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus); } -#umb-login-form umb-login-input:hover:not(:focus-within) { +#umb-login-form input:hover:not(:focus-within) { border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2)); } + +#umb-login-form input::-ms-reveal { + display: none; +} + +#umb-login-form input span { + position: absolute; + right: 1px; + top: 50%; + transform: translateY(-50%); + z-index: 100; +} + +#umb-login-form input span svg { + background-color: white; + display: block; + padding: .2em; + width: 1.3em; + height: 1.3em; +} diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts index a22e5e840e..3f5bfd1428 100644 --- a/src/Umbraco.Web.UI.Login/src/auth.element.ts +++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts @@ -5,9 +5,7 @@ import { until } from 'lit/directives/until.js'; import { umbAuthContext } from './context/auth.context.js'; import { umbLocalizationContext } from './external/localization/localization-context.js'; -import { UmbLocalizeElement } from './external/localization/localize.element.js'; -import type { UmbLoginInputElement } from './components/login-input.element.js'; -import type { InputType, UUIFormLayoutItemElement, UUILabelElement } from '@umbraco-ui/uui'; +import type { InputType, UUIFormLayoutItemElement } from '@umbraco-ui/uui'; import authStyles from './auth-styles.css?inline'; @@ -16,35 +14,35 @@ const createInput = (opts: { type: InputType; name: string; autocomplete: AutoFill; - requiredMessage: string; label: string; inputmode: string; + autofocus?: boolean; }) => { - const input = document.createElement('umb-login-input'); + const input = document.createElement('input'); input.type = opts.type; input.name = opts.name; input.autocomplete = opts.autocomplete; input.id = opts.id; input.required = true; - input.requiredMessage = opts.requiredMessage; - input.label = opts.label; - input.spellcheck = false; input.inputMode = opts.inputmode; + input.ariaLabel = opts.label; + input.autofocus = opts.autofocus || false; return input; }; -const createLabel = (opts: { forId: string; localizeAlias: string }) => { - const label = document.createElement('uui-label'); - const umbLocalize = document.createElement('umb-localize') as UmbLocalizeElement; +const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallback: string; }) => { + const label = document.createElement('label'); + const umbLocalize: any = document.createElement('umb-localize'); umbLocalize.key = opts.localizeAlias; - label.for = opts.forId; + umbLocalize.innerHTML = opts.localizeFallback; + label.htmlFor = opts.forId; label.appendChild(umbLocalize); return label; }; -const createFormLayoutItem = (label: UUILabelElement, input: UmbLoginInputElement) => { +const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement) => { const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement; formLayoutItem.appendChild(label); formLayoutItem.appendChild(input); @@ -115,10 +113,10 @@ export default class UmbAuthElement extends LitElement { _form?: HTMLFormElement; _usernameLayoutItem?: UUIFormLayoutItemElement; _passwordLayoutItem?: UUIFormLayoutItemElement; - _usernameInput?: UmbLoginInputElement; - _passwordInput?: UmbLoginInputElement; - _usernameLabel?: UUILabelElement; - _passwordLabel?: UUILabelElement; + _usernameInput?: HTMLInputElement; + _passwordInput?: HTMLInputElement; + _usernameLabel?: HTMLLabelElement; + _passwordLabel?: HTMLLabelElement; constructor() { super(); @@ -161,31 +159,30 @@ export default class UmbAuthElement extends LitElement { ? await umbLocalizationContext.localize('general_email', undefined, 'Email') : await umbLocalizationContext.localize('general_username', undefined, 'Username'); const labelPassword = await umbLocalizationContext.localize('general_password', undefined, 'Password'); - const requiredMessage = await umbLocalizationContext.localize('general_required', undefined, 'Required'); this._usernameInput = createInput({ id: 'username-input', type: 'text', name: 'username', autocomplete: 'username', - requiredMessage, label: labelUsername, inputmode: this.usernameIsEmail ? 'email' : '', + autofocus: true, }); this._passwordInput = createInput({ id: 'password-input', type: 'password', name: 'password', autocomplete: 'current-password', - requiredMessage, label: labelPassword, inputmode: '', }); this._usernameLabel = createLabel({ forId: 'username-input', - localizeAlias: this.usernameIsEmail ? 'general_email' : 'general_username', + localizeAlias: this.usernameIsEmail ? 'general_email' : 'auth_username', + localizeFallback: this.usernameIsEmail ? 'Email' : 'Username', }); - this._passwordLabel = createLabel({forId: 'password-input', localizeAlias: 'general_password'}); + this._passwordLabel = createLabel({forId: 'password-input', localizeAlias: 'general_password', localizeFallback: 'Password'}); this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput); this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput); diff --git a/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts b/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts deleted file mode 100644 index d3e6262172..0000000000 --- a/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { UUIInputElement } from '@umbraco-ui/uui'; -import { customElement } from 'lit/decorators.js'; - -/** - * This is a custom element based on UUIInputElement that is used in the login page. - * It differs from UUIInputElement in that it does not render a Shadow DOM. - * - * @element umb-login-input - * @inheritDoc UUIInputElement - */ -@customElement('umb-login-input') -export class UmbLoginInputElement extends UUIInputElement { - /** - * Remove the id attribute from the inner input element to avoid duplicate ids. - * - * @override - * @protected - */ - protected firstUpdated() { - const innerInput = this.querySelector('input'); - innerInput?.removeAttribute('id'); - - innerInput?.addEventListener('mousedown', () => { - this.style.setProperty('--uui-show-focus-outline', '0'); - }); - innerInput?.addEventListener('blur', () => { - this.style.setProperty('--uui-show-focus-outline', ''); - }); - } - - /** - * Since this element does not render a Shadow DOM nor does it have a unique ID, - * we need to override this method to get the form element. - * - * @override - * @protected - */ - protected getFormElement(): HTMLElement { - const formElement = this.querySelector('input'); - - if (!formElement) { - throw new Error('Form element not found'); - } - - return formElement; - } - - /** - * Instruct Lit to not render a Shadow DOM. - * - * @protected - */ - protected createRenderRoot() { - return this; - } - - static styles = [...UUIInputElement.styles]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-login-input': UmbLoginInputElement; - } -} diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts index 196db95d26..cf231e1acc 100644 --- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts +++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts @@ -36,6 +36,13 @@ export default class UmbLoginPageElement extends LitElement { if (!this.#formElement) return; + // We need to listen for the enter key to submit the form, because the uui-button does not support the native input fields submit event + this.#formElement.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + this.#onSubmitClick(); + } + }); + this.#formElement.onsubmit = this.#handleSubmit; } @@ -53,6 +60,12 @@ export default class UmbLoginPageElement extends LitElement { const password = formData.get('password') as string; const persist = formData.has('persist'); + if (!username || !password) { + this._loginError = await umbLocalizationContext.localize('auth_userFailedLogin'); + this._loginState = 'failed'; + return; + } + this._loginState = 'waiting'; const response = await umbAuthContext.login({ diff --git a/src/Umbraco.Web.UI.Login/src/index.ts b/src/Umbraco.Web.UI.Login/src/index.ts index 7e95f9a442..765843201b 100644 --- a/src/Umbraco.Web.UI.Login/src/index.ts +++ b/src/Umbraco.Web.UI.Login/src/index.ts @@ -15,7 +15,6 @@ import './components/external-login-provider.element.js'; import './components/layouts/new-password-layout.element.js'; import './components/layouts/confirmation-layout.element.js'; import './components/layouts/error-layout.element.js'; -import './components/login-input.element.js'; import './external/icon.registry.js'; import './external/custom-view.element.js'; From a6253957c8715eb459847c21e4f6f9b5ae7ee9f9 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 18 Oct 2024 09:17:17 +0200 Subject: [PATCH 47/95] MNTP: Improve site and root context for dynamic root (#17303) --- .../DynamicRoot/Origin/RootDynamicRootOriginFinder.cs | 4 +++- .../DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/DynamicRoot/Origin/RootDynamicRootOriginFinder.cs b/src/Umbraco.Core/DynamicRoot/Origin/RootDynamicRootOriginFinder.cs index 44766fb2dc..c5c28ffcfa 100644 --- a/src/Umbraco.Core/DynamicRoot/Origin/RootDynamicRootOriginFinder.cs +++ b/src/Umbraco.Core/DynamicRoot/Origin/RootDynamicRootOriginFinder.cs @@ -27,7 +27,9 @@ public class RootDynamicRootOriginFinder : IDynamicRootOriginFinder return null; } - var entity = _entityService.Get(query.Context.ParentKey); + // when creating new content, CurrentKey will be null - fallback to using ParentKey + Guid entityKey = query.Context.CurrentKey ?? query.Context.ParentKey; + var entity = _entityService.Get(entityKey); if (entity is null || _allowedObjectTypes.Contains(entity.NodeObjectType) is false) { diff --git a/src/Umbraco.Core/DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs b/src/Umbraco.Core/DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs index d1e515de59..f9b207db03 100644 --- a/src/Umbraco.Core/DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs +++ b/src/Umbraco.Core/DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs @@ -20,12 +20,14 @@ public class SiteDynamicRootOriginFinder : RootDynamicRootOriginFinder public override Guid? FindOriginKey(DynamicRootNodeQuery query) { - if (query.OriginAlias != SupportedOriginType || query.Context.CurrentKey.HasValue is false) + if (query.OriginAlias != SupportedOriginType) { return null; } - IEntitySlim? entity = _entityService.Get(query.Context.CurrentKey.Value); + // when creating new content, CurrentKey will be null - fallback to using ParentKey + Guid entityKey = query.Context.CurrentKey ?? query.Context.ParentKey; + IEntitySlim? entity = _entityService.Get(entityKey); if (entity is null || entity.NodeObjectType != Constants.ObjectTypes.Document) { return null; From eab27123dde740d8af69c1a27c655966917f7ee1 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 18 Oct 2024 09:39:29 +0200 Subject: [PATCH 48/95] MNTP: Re-initialize contextual dialog options upon content creation (#17301) --- .../contentpicker/contentpicker.controller.js | 135 ++++++++++-------- 1 file changed, 75 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js index a6fc601d8f..c5b476d6c5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/contentpicker/contentpicker.controller.js @@ -15,7 +15,7 @@ * @param {any} editorService * @param {any} userService */ -function contentPickerController($scope, $q, $routeParams, $location, entityResource, editorState, iconHelper, navigationService, localizationService, editorService, userService, overlayService) { +function contentPickerController($scope, $q, $routeParams, $location, entityResource, editorState, iconHelper, navigationService, localizationService, editorService, userService, overlayService, eventsService) { var vm = { labels: { @@ -25,6 +25,7 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso }; var unsubscribe; + var savedEventUnsubscribe; function subscribe() { unsubscribe = $scope.$on("formSubmitting", function (ev, args) { @@ -33,6 +34,13 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso }); $scope.model.value = trim(currIds.join(), ","); }); + + const initialEditorStateId = editorState?.current ? editorState.current.id : 0; + savedEventUnsubscribe = eventsService.on("content.saved", function () { + if($scope.invalidStartNode === true && editorState?.current && editorState.current.id !== initialEditorStateId) { + initDialogOptions(); + } + }); } function trim(str, chr) { @@ -214,69 +222,73 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso }); } - //We need to manually handle the filter for members here since the tree displayed is different and only contains - // searchable list views - if (entityType === "Member") { - //first change the not allowed filter css class - dialogOptions.filterCssClass = "not-allowed"; - var currFilter = dialogOptions.filter; - //now change the filter to be a method - dialogOptions.filter = function (i) { - //filter out the list view nodes - if (i.metaData.isContainer) { - return true; - } - if (!currFilter) { - return false; - } - //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, - // but not much we can do about that since members require special filtering. - var filterItem = currFilter.toLowerCase().split(','); - // NOTE: when used in a mini list view, the item content type alias is metaData.ContentTypeAlias (in regular views it's metaData.contentType) - var itemContentType = i.metaData.contentType || i.metaData.ContentTypeAlias; - var found = filterItem.indexOf(itemContentType.toLowerCase()) >= 0; - if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { - return true; - } + function initDialogOptions() { + //We need to manually handle the filter for members here since the tree displayed is different and only contains + // searchable list views + if (entityType === "Member") { + //first change the not allowed filter css class + dialogOptions.filterCssClass = "not-allowed"; + var currFilter = dialogOptions.filter; + //now change the filter to be a method + dialogOptions.filter = function (i) { + //filter out the list view nodes + if (i.metaData.isContainer) { + return true; + } + if (!currFilter) { + return false; + } + //now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller, + // but not much we can do about that since members require special filtering. + var filterItem = currFilter.toLowerCase().split(','); + // NOTE: when used in a mini list view, the item content type alias is metaData.ContentTypeAlias (in regular views it's metaData.contentType) + var itemContentType = i.metaData.contentType || i.metaData.ContentTypeAlias; + var found = filterItem.indexOf(itemContentType.toLowerCase()) >= 0; + if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) { + return true; + } - return false; - } - } - if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { - //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query - dialogOptions.startNodeId = -1; - } - else if ($scope.model.config.startNode.query) { - entityResource.getByXPath( - $scope.model.config.startNode.query, - editorState.current.id, - editorState.current.parentId, - "Document" - ).then(function (ent) { - dialogOptions.startNodeId = ($scope.model.config.idType === "udi" ? ent.udi : ent.id).toString(); - }); - } - else if ($scope.model.config.startNode.dynamicRoot) { - - entityResource.getDynamicRoot( - JSON.stringify($scope.model.config.startNode.dynamicRoot), - editorState.current.id, - editorState.current.parentId, - $scope.model.culture, - $scope.model.segment - ).then(function (ent) { - if(ent) { - dialogOptions.startNodeId = ($scope.model.config.idType === "udi" ? ent.udi : ent.id).toString(); - } else { - console.error("The Dynamic Root query did not find any valid results"); - $scope.invalidStartNode = true; + return false; } - }); + } + if ($routeParams.section === "settings" && $routeParams.tree === "documentTypes") { + //if the content-picker is being rendered inside the document-type editor, we don't need to process the startnode query + dialogOptions.startNodeId = -1; + } + else if ($scope.model.config.startNode.query) { + entityResource.getByXPath( + $scope.model.config.startNode.query, + editorState.current.id, + editorState.current.parentId, + "Document" + ).then(function (ent) { + dialogOptions.startNodeId = ($scope.model.config.idType === "udi" ? ent.udi : ent.id).toString(); + }); + } + else if ($scope.model.config.startNode.dynamicRoot) { + $scope.invalidStartNode = false; + entityResource.getDynamicRoot( + JSON.stringify($scope.model.config.startNode.dynamicRoot), + editorState.current.id, + editorState.current.parentId, + $scope.model.culture, + $scope.model.segment + ).then(function (ent) { + if(ent) { + dialogOptions.startNodeId = ($scope.model.config.idType === "udi" ? ent.udi : ent.id).toString(); + } else { + console.error("The Dynamic Root query did not find any valid results"); + $scope.invalidStartNode = true; + } + }); + } + + else { + dialogOptions.startNodeId = $scope.model.config.startNode.id; + } } - else { - dialogOptions.startNodeId = $scope.model.config.startNode.id; - } + initDialogOptions(); //dialog $scope.openCurrentPicker = function () { @@ -426,6 +438,9 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso if (unsubscribe) { unsubscribe(); } + if (savedEventUnsubscribe) { + savedEventUnsubscribe(); + } }); function setDirty() { From 4be183a20d36e1ddb02627a80fb32cde7e8e601e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 21 Oct 2024 09:49:34 +0200 Subject: [PATCH 49/95] Do not rely on NuCache to do key/id lookups (#17291) * Fixes #17182 * Ensure wait-on is installed --- build/azure-pipelines.yml | 4 ++++ .../UmbracoBuilderExtensions.cs | 18 +----------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index dc5ef96bde..2ec84196b0 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -506,6 +506,10 @@ stages: condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) workingDirectory: $(Agent.BuildDirectory)/app + # Ensures we have the package wait-on installed + - pwsh: npm install wait-on + displayName: Install wait-on package + # Wait for application to start responding to requests - pwsh: npx wait-on -v --interval 1000 --timeout 120000 $(ASPNETCORE_URLS) displayName: Wait for application diff --git a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs index 0c1042c3ff..862bbb9abc 100644 --- a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs @@ -37,23 +37,7 @@ public static class UmbracoBuilderExtensions builder.Services.TryAddSingleton(); builder.Services.TryAddTransient(); - // replace this service since we want to improve the content/media - // mapping lookups if we are using nucache. - // TODO: Gotta wonder how much this does actually improve perf? It's a lot of weird code to make this happen so hope it's worth it - builder.Services.AddUnique(factory => - { - var idkSvc = new IdKeyMap( - factory.GetRequiredService(), - factory.GetRequiredService()); - if (factory.GetRequiredService() is PublishedSnapshotService - publishedSnapshotService) - { - idkSvc.SetMapper(UmbracoObjectTypes.Document, id => publishedSnapshotService.GetDocumentUid(id), uid => publishedSnapshotService.GetDocumentId(uid)); - idkSvc.SetMapper(UmbracoObjectTypes.Media, id => publishedSnapshotService.GetMediaUid(id), uid => publishedSnapshotService.GetMediaId(uid)); - } - - return idkSvc; - }); + builder.Services.AddUnique(); builder.AddNuCacheNotifications(); From 1e32d59ecbfc2745f598946639f23a7d928f8b03 Mon Sep 17 00:00:00 2001 From: Justin Neville <67802060+justin-nevitech@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:53:17 +0100 Subject: [PATCH 50/95] Query for media using the full file path as well as the original file path for files that contain the sizes in the file name (i.e. image_200x200.jpg) (#17314) --- .../Persistence/Repositories/Implement/MediaRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index 73cb423837..ca488f8cc2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -305,20 +305,20 @@ public class MediaRepository : ContentRepositoryBase sql = GetBaseQuery(QueryType.Single, joinMediaVersion: true) - .Where(x => x.Path == umbracoFileValue) + .Where(x => x.Path == mediaPath || (isResized && x.Path == originalMediaPath)) .SelectTop(1); ContentDto? dto = Database.Fetch(sql).FirstOrDefault(); From 11270eaaf5f9bf2cca9bd5298c6211ce730fb0fd Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:00:47 +0200 Subject: [PATCH 51/95] Updated message pack (#17320) --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 4d6bf155d3..643c331edb 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -13,7 +13,7 @@ - + From 8a22672c7f31c0757c71cfaf5c4e516fbb76d8e4 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:15:59 +0200 Subject: [PATCH 52/95] Updated version of messagepack (#17321) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 43435394c1..b70236ab80 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -51,7 +51,7 @@ - + From 6399f235f2705a728ce3a7c0838d0a987ecdf95a Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:15:59 +0200 Subject: [PATCH 53/95] Updated version of messagepack (#17321) (cherry picked from commit 8a22672c7f31c0757c71cfaf5c4e516fbb76d8e4) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1096af77d6..027876da6f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -52,7 +52,7 @@ - + From edd0a4a4a926e6ff16d4978eb45dbca83bc8e0a8 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:00:47 +0200 Subject: [PATCH 54/95] Updated message pack (#17320) (cherry picked from commit 11270eaaf5f9bf2cca9bd5298c6211ce730fb0fd) --- src/Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 0ea425c69a..97fd24618c 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -13,7 +13,7 @@ - + From 97d74f39e75fb2b81f2325e31248a58ff59283f8 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:15:59 +0200 Subject: [PATCH 55/95] Updated version of messagepack (#17321) (cherry picked from commit 8a22672c7f31c0757c71cfaf5c4e516fbb76d8e4) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index fb5de6c698..656325376c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -51,7 +51,7 @@ - + From 6caf53ed2e62762ae07f2b8a73403f30594b42fa Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:15:59 +0200 Subject: [PATCH 56/95] Updated version of messagepack (#17321) (cherry picked from commit 8a22672c7f31c0757c71cfaf5c4e516fbb76d8e4) (cherry picked from commit 6399f235f2705a728ce3a7c0838d0a987ecdf95a) --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 1096af77d6..027876da6f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -52,7 +52,7 @@ - + From 67a71f8f823b2b8c2c62f89193ebb96bc883a091 Mon Sep 17 00:00:00 2001 From: Elitsa Date: Wed, 21 Aug 2024 14:41:14 +0200 Subject: [PATCH 57/95] Make sure that the client shows the login screen as close to the server's timout time as possible --- .../src/common/services/user.service.js | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index ee9aa0864f..943141878a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -3,6 +3,7 @@ angular.module('umbraco.services') var currentUser = null; var lastUserId = null; + var countdownCounter = null; //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server // this is used so that we know when to go and get the user's remaining seconds directly. @@ -43,6 +44,10 @@ angular.module('umbraco.services') } currentUser = usr; lastServerTimeoutSet = new Date(); + //don't start the timer if it is already going + if (countdownCounter) { + return; + } //start the timer countdownUserTimeout(); } @@ -54,7 +59,7 @@ angular.module('umbraco.services') */ function countdownUserTimeout() { - $timeout(function () { + countdownCounter = $timeout(function () { if (currentUser) { //countdown by 5 seconds since that is how long our timer is for. @@ -95,15 +100,20 @@ angular.module('umbraco.services') if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) angularHelper.safeApply($rootScope, function () { - try { - //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we - // don't actually care about this result. - authResource.getRemainingTimeoutSeconds(); - } - finally { - userAuthExpired(); - } + //NOTE: We are calling this again so that the server can create a log that the timeout has expired + //and we will show the login screen as close to the server's timout time as possible + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + + //the client auth can expire a second earlier as the client internal clock is behind + if (result < 1) { + userAuthExpired(); + } + }); }); + + //recurse the countdown! + countdownUserTimeout(); } else { //we've got less than 30 seconds remaining so let's check the server @@ -155,6 +165,7 @@ angular.module('umbraco.services') lastServerTimeoutSet = null; currentUser = null; + countdownCounter = null; openLoginDialog(isLogout === undefined ? true : !isLogout); } From c9021ab2d2c02f23ad5d9bcff3ccb0f5e283d914 Mon Sep 17 00:00:00 2001 From: Elitsa Date: Wed, 21 Aug 2024 14:44:09 +0200 Subject: [PATCH 58/95] Reduce the time when getRemainingTimeoutSeconds request is made from 30s to 20s, so fewer calls are made --- .../src/common/services/user.service.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 943141878a..bb56b7cc20 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -65,17 +65,17 @@ angular.module('umbraco.services') //countdown by 5 seconds since that is how long our timer is for. currentUser.remainingAuthSeconds -= 5; - //if there are more than 30 remaining seconds, recurse! - if (currentUser.remainingAuthSeconds > 30) { + //if there are more than 20 remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 20) { //we need to check when the last time the timeout was set from the server, if - // it has been more than 30 seconds then we'll manually go and retrieve it from the + // it has been more than 20 seconds then we'll manually go and retrieve it from the // server - this helps to keep our local countdown in check with the true timeout. if (lastServerTimeoutSet != null) { var now = new Date(); var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - if (seconds > 30) { + if (seconds > 20) { //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. @@ -116,7 +116,7 @@ angular.module('umbraco.services') countdownUserTimeout(); } else { - //we've got less than 30 seconds remaining so let's check the server + //we've got less than 20 seconds remaining so let's check the server if (lastServerTimeoutSet != null) { //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we From 8c1128c85b8f84fee6891e19967d87aaa8ba80ed Mon Sep 17 00:00:00 2001 From: Elitsa Date: Tue, 20 Aug 2024 10:15:37 +0200 Subject: [PATCH 59/95] Update the HttpContext's user with the authenticated user's principal --- .../Extensions/HttpContextExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs index 226755039e..2a6bbc99d4 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -59,6 +59,14 @@ public static class HttpContextExtensions await httpContext.AuthenticateAsync(Constants.Security.BackOfficeExternalAuthenticationType); } + // Update the HttpContext's user with the authenticated user's principal to ensure + // that subsequent requests within the same context will recognize the user + // as authenticated. + if (result.Succeeded) + { + httpContext.User = result.Principal; + } + return result; } From 35c51a029a16435109b3462f5db4cbdeb9189408 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Mon, 12 Aug 2024 14:48:32 +0100 Subject: [PATCH 60/95] Prevents XSS when viewing an uploaded SVG from the media-info and image-preview components. --- .../media/umbmedianodeinfo.directive.js | 10 +------ .../common/services/mediahelper.service.js | 26 +++++++++++++++++-- .../umbimagepreview/umb-image-preview.html | 2 +- .../umbimagepreview.controller.js | 26 +++++++++---------- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 2a65c67a8d..32abdc2a48 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -69,15 +69,7 @@ editorService.mediaTypeEditor(editor); }; - scope.openSVG = () => { - var popup = window.open('', '_blank'); - var html = '' + - ''; - - popup.document.open(); - popup.document.write(html); - popup.document.close(); - } + scope.openSVG = () => mediaHelper.openSVG(scope.nodeUrl); // watch for content updates - reload content when node is saved, published etc. scope.$watch('node.updateDate', function(newValue, oldValue){ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index e98a597e76..734dd29cba 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper, $http, $log) { +function mediaHelper(umbRequestHelper, $http, $log, $location) { //container of fileresolvers var _mediaFileResolvers = {}; @@ -449,7 +449,29 @@ function mediaHelper(umbRequestHelper, $http, $log) { cropY2: options.crop ? options.crop.y2 : null })), "Failed to retrieve processed image URL for image: " + imagePath); - } + }, + + /** + * @ngdoc function + * @name umbraco.services.mediaHelper#openSVG + * @methodOf umbraco.services.mediaHelper + * @function + * + * @description + * Opens an SVG file in a new window as an image file, to prevent any potential XSS exploits. + * + * @param {string} imagePath File path, ex /media/1234/my-image.svg + */ + openSVG: function (imagePath) { + var popup = window.open('', '_blank'); + var html = '' + + '' + + ''; + + popup.document.open(); + popup.document.write(html); + popup.document.close(); + } }; } angular.module('umbraco.services').factory('mediaHelper', mediaHelper); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html index 989f8ef093..0918f6dc5b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html @@ -1,6 +1,6 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js index 36eb3958e2..d1f32fb6b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js @@ -1,18 +1,16 @@ - - - - angular.module("umbraco") - .controller("umbImagePreviewController", - function (mediaHelper) { + .controller("umbImagePreviewController", + function (mediaHelper) { - var vm = this; + var vm = this; - vm.getThumbnail = function(source) { - return mediaHelper.getThumbnailFromPath(source) || source; - } - vm.getClientSideUrl = function(sourceData) { - return URL.createObjectURL(sourceData); - } + vm.getThumbnail = function (source) { + return mediaHelper.getThumbnailFromPath(source) || source; + } - }); + vm.getClientSideUrl = function (sourceData) { + return URL.createObjectURL(sourceData); + } + + vm.openSVG = (source) => mediaHelper.openSVG(source); + }); From 2ad5ecd5d508ee4404ef604f6e232cdfd65a6a10 Mon Sep 17 00:00:00 2001 From: Elitsa Date: Wed, 21 Aug 2024 14:41:14 +0200 Subject: [PATCH 61/95] Make sure that the client shows the login screen as close to the server's timout time as possible --- .../src/common/services/user.service.js | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 71141106bf..9fbee2fa92 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -3,6 +3,7 @@ angular.module('umbraco.services') var currentUser = null; var lastUserId = null; + var countdownCounter = null; //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server // this is used so that we know when to go and get the user's remaining seconds directly. @@ -43,6 +44,10 @@ angular.module('umbraco.services') } currentUser = usr; lastServerTimeoutSet = new Date(); + //don't start the timer if it is already going + if (countdownCounter) { + return; + } //start the timer countdownUserTimeout(); } @@ -54,7 +59,7 @@ angular.module('umbraco.services') */ function countdownUserTimeout() { - $timeout(function () { + countdownCounter = $timeout(function () { if (currentUser) { //countdown by 5 seconds since that is how long our timer is for. @@ -95,15 +100,20 @@ angular.module('umbraco.services') if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) angularHelper.safeApply($rootScope, function () { - try { - //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we - // don't actually care about this result. - authResource.getRemainingTimeoutSeconds(); - } - finally { - userAuthExpired(); - } + //NOTE: We are calling this again so that the server can create a log that the timeout has expired + //and we will show the login screen as close to the server's timout time as possible + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + + //the client auth can expire a second earlier as the client internal clock is behind + if (result < 1) { + userAuthExpired(); + } + }); }); + + //recurse the countdown! + countdownUserTimeout(); } else { //we've got less than 30 seconds remaining so let's check the server @@ -155,6 +165,7 @@ angular.module('umbraco.services') lastServerTimeoutSet = null; currentUser = null; + countdownCounter = null; if (!isLogout) { openLoginDialog(true); From d83daf44d3642df0d102eb4a90aa6b6b17c9311e Mon Sep 17 00:00:00 2001 From: Elitsa Date: Wed, 21 Aug 2024 14:44:09 +0200 Subject: [PATCH 62/95] Reduce the time when getRemainingTimeoutSeconds request is made from 30s to 20s, so fewer calls are made --- .../src/common/services/user.service.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 9fbee2fa92..41965d5b3e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -65,17 +65,17 @@ angular.module('umbraco.services') //countdown by 5 seconds since that is how long our timer is for. currentUser.remainingAuthSeconds -= 5; - //if there are more than 30 remaining seconds, recurse! - if (currentUser.remainingAuthSeconds > 30) { + //if there are more than 20 remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 20) { //we need to check when the last time the timeout was set from the server, if - // it has been more than 30 seconds then we'll manually go and retrieve it from the + // it has been more than 20 seconds then we'll manually go and retrieve it from the // server - this helps to keep our local countdown in check with the true timeout. if (lastServerTimeoutSet != null) { var now = new Date(); var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - if (seconds > 30) { + if (seconds > 20) { //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. @@ -116,7 +116,7 @@ angular.module('umbraco.services') countdownUserTimeout(); } else { - //we've got less than 30 seconds remaining so let's check the server + //we've got less than 20 seconds remaining so let's check the server if (lastServerTimeoutSet != null) { //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we From 1bc5466a8ded0ec87e06a9d9da35f49d62fe080b Mon Sep 17 00:00:00 2001 From: Elitsa Date: Tue, 20 Aug 2024 10:15:37 +0200 Subject: [PATCH 63/95] Update the HttpContext's user with the authenticated user's principal --- .../Extensions/HttpContextExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs index 0f2da0ac4e..0a84f318f6 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -59,6 +59,14 @@ public static class HttpContextExtensions await httpContext.AuthenticateAsync(Constants.Security.BackOfficeExternalAuthenticationType); } + // Update the HttpContext's user with the authenticated user's principal to ensure + // that subsequent requests within the same context will recognize the user + // as authenticated. + if (result.Succeeded) + { + httpContext.User = result.Principal; + } + return result; } From 3431f763201b0befcd74e26686e9c41ef766e1de Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Mon, 12 Aug 2024 15:25:05 +0100 Subject: [PATCH 64/95] Prevents XSS when viewing an uploaded SVG from the media-info and image-preview components. --- .../media/umbmedianodeinfo.directive.js | 10 +------ .../common/services/mediahelper.service.js | 26 +++++++++++++++++-- .../umbimagepreview/umb-image-preview.html | 2 +- .../umbimagepreview.controller.js | 26 +++++++++---------- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js index 30bb2b6f3f..e752044ddc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/media/umbmedianodeinfo.directive.js @@ -136,15 +136,7 @@ editorService.mediaTypeEditor(editor); }; - scope.openSVG = () => { - var popup = window.open('', '_blank'); - var html = '' + - ''; - - popup.document.open(); - popup.document.write(html); - popup.document.close(); - } + scope.openSVG = () => mediaHelper.openSVG(scope.nodeUrl); // watch for content updates - reload content when node is saved, published etc. scope.$watch('node.updateDate', function(newValue, oldValue){ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index 14de3bb1c4..b9e6560f81 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -3,7 +3,7 @@ * @name umbraco.services.mediaHelper * @description A helper object used for dealing with media items **/ -function mediaHelper(umbRequestHelper, $http, $log) { +function mediaHelper(umbRequestHelper, $http, $log, $location) { //container of fileresolvers var _mediaFileResolvers = {}; @@ -449,7 +449,29 @@ function mediaHelper(umbRequestHelper, $http, $log) { cropY2: options.crop ? options.crop.y2 : null })), "Failed to retrieve processed image URL for image: " + imagePath); - } + }, + + /** + * @ngdoc function + * @name umbraco.services.mediaHelper#openSVG + * @methodOf umbraco.services.mediaHelper + * @function + * + * @description + * Opens an SVG file in a new window as an image file, to prevent any potential XSS exploits. + * + * @param {string} imagePath File path, ex /media/1234/my-image.svg + */ + openSVG: function (imagePath) { + var popup = window.open('', '_blank'); + var html = '' + + '' + + ''; + + popup.document.open(); + popup.document.write(html); + popup.document.close(); + } }; } angular.module('umbraco.services').factory('mediaHelper', mediaHelper); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html index 989f8ef093..0918f6dc5b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umb-image-preview.html @@ -1,6 +1,6 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js index 36eb3958e2..d1f32fb6b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/components/media/umbimagepreview/umbimagepreview.controller.js @@ -1,18 +1,16 @@ - - - - angular.module("umbraco") - .controller("umbImagePreviewController", - function (mediaHelper) { + .controller("umbImagePreviewController", + function (mediaHelper) { - var vm = this; + var vm = this; - vm.getThumbnail = function(source) { - return mediaHelper.getThumbnailFromPath(source) || source; - } - vm.getClientSideUrl = function(sourceData) { - return URL.createObjectURL(sourceData); - } + vm.getThumbnail = function (source) { + return mediaHelper.getThumbnailFromPath(source) || source; + } - }); + vm.getClientSideUrl = function (sourceData) { + return URL.createObjectURL(sourceData); + } + + vm.openSVG = (source) => mediaHelper.openSVG(source); + }); From e36dc1f554ca86f4a0fec8211e77c4cd7cc47dc4 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:26:59 +0700 Subject: [PATCH 65/95] V14 QA Added the acceptance tests for rendering content with different value (#17293) * Added tests for rendering content with numeric * Added tests for rendering content with textarea * Added tests for rendering content with approved color * Added tests for rendering content with numeric * Added tests for rendering content with tags * Added tests for rendering content with textarea * Updated tests for rendering content with textstring due to test helper changes * Added tests for rendering content with truefalse * Bumped version of test helper * Make all tests for rendering content run in the pipeline * Fixed comments * Removed blank lines * Fixed name * Make all smoke tests run in the pipeline --- .../package-lock.json | 16 +++---- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../RenderingContentWithApprovedColor.spec.ts | 48 +++++++++++++++++++ .../RenderingContentWithNumeric.spec.ts | 40 ++++++++++++++++ .../RenderingContentWithTags.spec.ts | 43 +++++++++++++++++ .../RenderingContentWithTextarea.spec.ts | 44 +++++++++++++++++ .../RenderingContentWithTextstring.spec.ts | 11 +++-- .../RenderingContentWithTrueFalse.spec.ts | 39 +++++++++++++++ 8 files changed, 229 insertions(+), 14 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 4ce3aee211..759237a074 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.21", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.90", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.91", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -55,19 +55,19 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.21.tgz", - "integrity": "sha512-/8jf444B8XjYMJ4mdun6Nc1GD6z4VTOAMi/foRKNwDu6H+UEVx8KcFfwel+M1rQOz1OULyIsf+XJDYc7TMujOA==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.22.tgz", + "integrity": "sha512-5GQT170ViEj9IBov3PvNU7qSJkCkNCTfE/5iXiDBKFsFnMH5XLG6qbgGf5xPYtg/GZ1uKJYgP5Ahq15YUf/DpA==", "dependencies": { "camelize": "^1.0.1" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.90", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.90.tgz", - "integrity": "sha512-H55F9gttpQR8Fah77gxWxX3S/PY539r82tQRDbo6GgPHeilSVmLXcgesZ+2cgLaAQ406D6JGDvJVqIZukmEzuQ==", + "version": "2.0.0-beta.91", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.91.tgz", + "integrity": "sha512-8oxr8N5rP3JkIVLH9fRNCoQpaRaolwiCZtogS1kn2vHHiTvnZznOI+UYCthA4LzbA9SYA1hSqofLSLfDTO/9lA==", "dependencies": { - "@umbraco/json-models-builders": "2.0.21", + "@umbraco/json-models-builders": "2.0.22", "node-fetch": "^2.6.7" } }, diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index f41b929af2..951329447d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -19,7 +19,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.21", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.90", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.91", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" 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 0000000000..63ae1dfd85 --- /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.doesContentRenderValueHaveText(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.doesContentRenderValueHaveText(colorValue.value); +}); \ 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 0000000000..d541317e53 --- /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.doesContentRenderValueHaveText(numericValue); + }); +} \ 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 0000000000..08f7f9bf4c --- /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.createTemplateWithDisplayingTagsValue(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.doesContentRenderValueHaveText(value); + }); + }); +} \ No newline at end of file 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 0000000000..ab2c86f901 --- /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.doesContentRenderValueHaveText(textareaValue); + }); +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts index 9a394ffd07..1ccdaec91e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts @@ -1,10 +1,11 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; const contentName = 'Test Rendering Content'; const documentTypeName = 'TestDocumentTypeForContent'; const dataTypeName = 'Textstring'; const templateName = 'TestTemplateForContent'; -let dataTypeData; +const propertyName = 'Test Textstring'; +let dataTypeData = null; test.beforeEach(async ({umbracoApi}) => { dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); @@ -29,7 +30,8 @@ for (const textstring of textstrings) { test(`can render content with ${textstring.type}`, async ({umbracoApi, umbracoUi}) => { // Arrange const textstringValue = textstring.value; - await umbracoApi.document.createPublishedDocumentWithValue(contentName, textstringValue, dataTypeData.id, documentTypeName, templateName); + 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; @@ -39,5 +41,4 @@ for (const textstring of textstrings) { // Assert await umbracoUi.contentRender.doesContentRenderValueHaveText(textstringValue); }); -} - +} \ No newline at end of file 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 0000000000..207da6bff4 --- /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.doesContentRenderValueHaveText(trueFalse.expectedValue); + }); +} \ No newline at end of file From 728dc899094944cbef9557703de20ea2ae3d0734 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:12:42 +0200 Subject: [PATCH 66/95] V14 QA Skip Users tests on Sqlite (#17330) * Split sqlite test because we run into db locks * Uses the new command --- build/azure-pipelines.yml | 2 +- build/nightly-E2E-test-pipelines.yml | 4 ++-- tests/Umbraco.Tests.AcceptanceTest/package.json | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index faa31ae511..2d6606c2c3 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -517,7 +517,7 @@ stages: workingDirectory: tests/Umbraco.Tests.AcceptanceTest # Test - - pwsh: npm run smokeTest --ignore-certificate-errors + - pwsh: npm run smokeTestSqlite --ignore-certificate-errors displayName: Run Playwright tests continueOnError: true workingDirectory: tests/Umbraco.Tests.AcceptanceTest diff --git a/build/nightly-E2E-test-pipelines.yml b/build/nightly-E2E-test-pipelines.yml index 6a069ef38c..38bd288859 100644 --- a/build/nightly-E2E-test-pipelines.yml +++ b/build/nightly-E2E-test-pipelines.yml @@ -207,9 +207,9 @@ stages: # Test - ${{ if eq(parameters.runSmokeTests, true) }}: - pwsh: npm run smokeTest --ignore-certificate-errors + pwsh: npm run smokeTestSqlite --ignore-certificate-errors ${{ else }}: - pwsh: npm run test --ignore-certificate-errors + pwsh: npm run testSqlite --ignore-certificate-errors displayName: Run Playwright tests continueOnError: true workingDirectory: tests/Umbraco.Tests.AcceptanceTest diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 951329447d..5170f824e1 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -6,9 +6,11 @@ "config": "node config.js", "ui": "npx playwright test --headed DefaultConfig", "test": "npx playwright test DefaultConfig", + "testSqlite": "npx playwright test DefaultConfig --grep-invert \"Users\"", "all": "npx playwright test", "createTest": "node createTest.js", - "smokeTest": "npx playwright test DefaultConfig --grep \"@smoke\"" + "smokeTest": "npx playwright test DefaultConfig --grep \"@smoke\"", + "smokeTestSqlite": "npx playwright test DefaultConfig --grep \"@smoke\" --grep-invert \"Users\"" }, "devDependencies": { "@playwright/test": "^1.43", From aa1f3df76b0aa9e78156407585830b496362e72f Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:37:24 +0200 Subject: [PATCH 67/95] Updated to match locator (#17334) --- .../tests/DefaultConfig/Login/login.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts index 64dfb64dca..bb1016b502 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Login/login.spec.ts @@ -18,8 +18,8 @@ test.describe('Login', () => { await expect(error).toBeHidden(); // Action - await page.fill('#username-input input', process.env.UMBRACO_USER_LOGIN); - await page.fill('#password-input input', process.env.UMBRACO_USER_PASSWORD); + await page.fill('#username-input', process.env.UMBRACO_USER_LOGIN); + await page.fill('#password-input', process.env.UMBRACO_USER_PASSWORD); await page.locator('#umb-login-button').click(); await page.waitForURL(process.env.URL + '/umbraco#/content'); @@ -37,8 +37,8 @@ test.describe('Login', () => { await expect(error).toBeHidden(); // Action - await page.fill('#username-input input', username); - await page.fill('#password-input input', password); + await page.fill('#username-input', username); + await page.fill('#password-input', password); await page.locator('#umb-login-button').click(); // Assert @@ -58,8 +58,8 @@ test.describe('Login', () => { await expect(error).toBeHidden(); // Action - await page.fill('#username-input input', username); - await page.fill('#password-input input', password); + await page.fill('#username-input', username); + await page.fill('#password-input', password); await page.locator('#umb-login-button').click(); // Assert From 73d70ba9fc39535af39af98c3284381dd3e5d54d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 24 Oct 2024 09:45:59 +0200 Subject: [PATCH 68/95] Swap to windows vm for build (#17348) (cherry picked from commit 199a2f4619b26170376708446e46ffd4f8075d98) --- build/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 61ae46a2b3..752f18fd0d 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -75,7 +75,7 @@ stages: - job: A displayName: Build Umbraco CMS pool: - vmImage: 'ubuntu-latest' + vmImage: 'windows-latest' steps: - checkout: self submodules: true From aa9f194d7611bb830a8fc3b80295c8115fb5b2d6 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:45:23 +0200 Subject: [PATCH 69/95] Format sql statement (#17354) --- .../SyntaxProvider/SqlServerSyntaxProviderTests.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs index be90d8695b..7e1d1f163f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/SyntaxProvider/SqlServerSyntaxProviderTests.cs @@ -68,12 +68,8 @@ public class SqlServerSyntaxProviderTests : UmbracoIntegrationTest } Assert.AreEqual( - @$"DELETE FROM {t("cmsContentNu")} WHERE {c("nodeId")} IN (SELECT {c("nodeId")} FROM (SELECT DISTINCT cmsContentNu.nodeId -FROM {t("cmsContentNu")} -INNER JOIN {t("umbracoNode")} -ON {t("cmsContentNu")}.{c("nodeId")} = {t("umbracoNode")}.{c("id")} -WHERE (({t("umbracoNode")}.{c("nodeObjectType")} = @0))) x)".Replace(Environment.NewLine, " ").Replace("\n", " ") - .Replace("\r", " "), + @$"DELETE FROM {t("cmsContentNu")} WHERE {c("nodeId")} IN (SELECT {c("nodeId")} FROM (SELECT DISTINCT cmsContentNu.nodeId FROM {t("cmsContentNu")} INNER JOIN {t("umbracoNode")} ON {t("cmsContentNu")}.{c("nodeId")} = {t("umbracoNode")}.{c("id")} WHERE (({t("umbracoNode")}.{c("nodeObjectType")} = @0))) x)".Replace(Environment.NewLine, " ") + .Replace("\n", " ").Replace("\r", " "), sqlOutput.SQL.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " ")); Assert.AreEqual(1, sqlOutput.Arguments.Length); From 11ccafeb97997794e05ce79be8d108e30ee67362 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:16:28 +0700 Subject: [PATCH 70/95] V14 QA Added tests for rendering content with checkboxlist and date picker (#17332) * Added tests for rendering content with numeric * Added tests for rendering content with textarea * Added tests for rendering content with approved color * Added tests for rendering content with numeric * Added tests for rendering content with tags * Added tests for rendering content with textarea * Updated tests for rendering content with textstring due to test helper changes * Added tests for rendering content with truefalse * Added tests for rendering content with checkbox list * Added tests for rendering content with date picker - not done * Updated tests for rendering content with date picker * Updated tests for rendering content due to ui helper changes * Bumped version * Removed blank lines * Make Rendering Content tests run in the pipeline * Changed method name due to test helper changes * Reverted --- .../package-lock.json | 10 ++--- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../RenderingContentWithApprovedColor.spec.ts | 6 +-- .../RenderingContentWithCheckboxList.spec.ts | 40 +++++++++++++++++++ .../RenderingContentWithDatePicker.spec.ts | 35 ++++++++++++++++ .../RenderingContentWithNumeric.spec.ts | 4 +- .../RenderingContentWithTags.spec.ts | 4 +- .../RenderingContentWithTextarea.spec.ts | 4 +- .../RenderingContentWithTextstring.spec.ts | 2 +- .../RenderingContentWithTrueFalse.spec.ts | 4 +- 10 files changed, 94 insertions(+), 19 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDatePicker.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 759237a074..def3e8bdb5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.21", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.91", + "@umbraco/json-models-builders": "^2.0.22", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.92", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -63,9 +63,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.91", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.91.tgz", - "integrity": "sha512-8oxr8N5rP3JkIVLH9fRNCoQpaRaolwiCZtogS1kn2vHHiTvnZznOI+UYCthA4LzbA9SYA1hSqofLSLfDTO/9lA==", + "version": "2.0.0-beta.92", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.92.tgz", + "integrity": "sha512-qfmuaT+J3PFnUUAeW04O8txxPY+eWc6Ue/2OvM6my0P2j//V5ACFtBxdnWtEylx6D83ga6uIphSHFsLJb0sy3g==", "dependencies": { "@umbraco/json-models-builders": "2.0.22", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 5170f824e1..f25f0abc71 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -20,8 +20,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.21", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.91", + "@umbraco/json-models-builders": "^2.0.22", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.92", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts index 63ae1dfd85..ba7fa53385 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts @@ -30,7 +30,7 @@ test('can render content with an approved color with label', async ({umbracoApi, await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); // Assert - await umbracoUi.contentRender.doesContentRenderValueHaveText(colorValue.label); + await umbracoUi.contentRender.doesContentRenderValueContainText(colorValue.label); }); test('can render content with an approved color without label', async ({umbracoApi, umbracoUi}) => { @@ -44,5 +44,5 @@ test('can render content with an approved color without label', async ({umbracoA await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); // Assert - await umbracoUi.contentRender.doesContentRenderValueHaveText(colorValue.value); -}); \ No newline at end of file + 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 0000000000..7f901fcd87 --- /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.createTemplateWithDisplayingCheckboxListValue(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/RenderingContentWithDatePicker.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDatePicker.spec.ts new file mode 100644 index 0000000000..52e49aeb31 --- /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/RenderingContentWithNumeric.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts index d541317e53..8a60b518f4 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithNumeric.spec.ts @@ -35,6 +35,6 @@ for (const numeric of numerics) { await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); // Assert - await umbracoUi.contentRender.doesContentRenderValueHaveText(numericValue); + await umbracoUi.contentRender.doesContentRenderValueContainText(numericValue); }); -} \ 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 index 08f7f9bf4c..f1cd346b66 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts @@ -37,7 +37,7 @@ for (const tag of tags) { // Assert tagValue.forEach(async value => { - await umbracoUi.contentRender.doesContentRenderValueHaveText(value); + await umbracoUi.contentRender.doesContentRenderValueContainText(value); }); }); -} \ No newline at end of file +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts index ab2c86f901..e4f43e51e4 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextarea.spec.ts @@ -39,6 +39,6 @@ for (const textarea of textareas) { await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); // Assert - await umbracoUi.contentRender.doesContentRenderValueHaveText(textareaValue); + await umbracoUi.contentRender.doesContentRenderValueContainText(textareaValue); }); -} \ No newline at end of file +} diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts index 1ccdaec91e..b0fd459563 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTextstring.spec.ts @@ -39,6 +39,6 @@ for (const textstring of textstrings) { await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); // Assert - await umbracoUi.contentRender.doesContentRenderValueHaveText(textstringValue); + await umbracoUi.contentRender.doesContentRenderValueContainText(textstringValue); }); } \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts index 207da6bff4..9c4164f321 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTrueFalse.spec.ts @@ -34,6 +34,6 @@ for (const trueFalse of trueFalseValues) { await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); // Assert - await umbracoUi.contentRender.doesContentRenderValueHaveText(trueFalse.expectedValue); + await umbracoUi.contentRender.doesContentRenderValueContainText(trueFalse.expectedValue); }); -} \ No newline at end of file +} From 76fcf19b152e2757024d65c935401dbaa94ec980 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:19:53 +0700 Subject: [PATCH 71/95] V14 QA Added acceptance tests for rendering content with Dropdown, Radiobutton and ImageCropper (#17357) * Added tests for rendering content with numeric * Added tests for rendering content with textarea * Added tests for rendering content with approved color * Added tests for rendering content with numeric * Added tests for rendering content with tags * Added tests for rendering content with textarea * Updated tests for rendering content with textstring due to test helper changes * Added tests for rendering content with truefalse * Bumped version of test helper * Make all tests for rendering content run in the pipeline * Fixed comments * Removed blank lines * Fixed name * Make all smoke tests run in the pipeline * Added tests for rendering content with dropdown * Added tests for rendering content with Image Cropper - not done * Updated tests for rendering content due to ui helper changes * Updated tests for rendering content with image cropper * Updated tests due to the api helper changes * Bumped version of test helper * Make all the tests for rendering content run in the pipeline * Removed blank lines * Format code * Fixed test name * Reverted --- .../package-lock.json | 18 ++++---- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../RenderingContentWithApprovedColor.spec.ts | 2 +- .../RenderingContentWithCheckboxList.spec.ts | 4 +- .../RenderingContentWithDropdown.spec.ts | 44 +++++++++++++++++++ .../RenderingContentWithImageCropper.spec.ts | 36 +++++++++++++++ .../RenderingContentWithRadiobox.spec.ts | 36 +++++++++++++++ .../RenderingContentWithTags.spec.ts | 2 +- 8 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDropdown.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithImageCropper.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithRadiobox.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index def3e8bdb5..ce584dba6f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.22", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.92", + "@umbraco/json-models-builders": "^2.0.23", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.95", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -55,19 +55,19 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.22.tgz", - "integrity": "sha512-5GQT170ViEj9IBov3PvNU7qSJkCkNCTfE/5iXiDBKFsFnMH5XLG6qbgGf5xPYtg/GZ1uKJYgP5Ahq15YUf/DpA==", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.23.tgz", + "integrity": "sha512-48TgQnrdxQ2Oi/NintzgYvVRnlX3JXKq505leuWATo9AH3ffuzR+g7hXoTC/Us9ms5BjTTXssLBwzlgCyHJTJQ==", "dependencies": { "camelize": "^1.0.1" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.92", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.92.tgz", - "integrity": "sha512-qfmuaT+J3PFnUUAeW04O8txxPY+eWc6Ue/2OvM6my0P2j//V5ACFtBxdnWtEylx6D83ga6uIphSHFsLJb0sy3g==", + "version": "2.0.0-beta.95", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.95.tgz", + "integrity": "sha512-9pJrC/4d4O/TKSyHk0n12ZBFi3lRJd6+kRsngK1eCqcHjVqB7HTDWjsL7KS+1bWzVqFvJgbCVezt2eR+GiWBEA==", "dependencies": { - "@umbraco/json-models-builders": "2.0.22", + "@umbraco/json-models-builders": "2.0.23", "node-fetch": "^2.6.7" } }, diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index f25f0abc71..85ef6be6d7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -20,8 +20,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.22", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.92", + "@umbraco/json-models-builders": "^2.0.23", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.95", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts index ba7fa53385..7e73452b4e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithApprovedColor.spec.ts @@ -45,4 +45,4 @@ test('can render content with an approved color without label', async ({umbracoA // Assert await umbracoUi.contentRender.doesContentRenderValueContainText(colorValue.value); -}); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts index 7f901fcd87..cc48bf55c3 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithCheckboxList.spec.ts @@ -24,7 +24,7 @@ for (const checkbox of checkboxList) { // Arrange const checkboxValue = checkbox.value; const dataTypeId = await umbracoApi.dataType.createCheckboxListDataType(customDataTypeName, checkboxValue); - const templateId = await umbracoApi.template.createTemplateWithDisplayingCheckboxListValue(templateName, AliasHelper.toAlias(propertyName)); + 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; @@ -37,4 +37,4 @@ for (const checkbox of checkboxList) { await umbracoUi.contentRender.doesContentRenderValueContainText(value); }); }); -} +} \ No newline at end of file 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 0000000000..73381004f3 --- /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 0000000000..b428f16530 --- /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/RenderingContentWithRadiobox.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithRadiobox.spec.ts new file mode 100644 index 0000000000..280a8ad727 --- /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 index f1cd346b66..6fe6e5b333 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithTags.spec.ts @@ -27,7 +27,7 @@ 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.createTemplateWithDisplayingTagsValue(templateName, AliasHelper.toAlias(propertyName)); + 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; From 41da2e2cf3b5d8503a20dd39ece16c5c08d140c9 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:53:33 +0700 Subject: [PATCH 72/95] V14 Added acceptance tests for the List View Media and custom data type in Content section (#17025) * Added Content tests with custom data type * Added tests for List View Media data type in Media section * Updated method name due to api helper changes * Updated the assertion of Content tests with custom data type * Bumped version of test helper * Make all Content tests run in the pipeline * Skipped test for code editor as it is removed * Fixed comment * Make Media tests running in the pipeline * Bumped version * Updated code due to ui helper changes * Bumped version of test helper * Updated tests for bulk trash in the media section * Fixed notification message * Make Content tests and Media tests run in the pipeline * Added more waits * Reverted --- .../Content/ContentWithCustomDataType.spec.ts | 317 ++++++++++++++++++ .../DefaultConfig/Media/ListViewMedia.spec.ts | 160 +++++++++ .../tests/DefaultConfig/Media/Media.spec.ts | 4 +- 3 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithCustomDataType.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts 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 0000000000..228275de08 --- /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/Media/ListViewMedia.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/ListViewMedia.spec.ts new file mode 100644 index 0000000000..37fef60b9f --- /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.createDefaultMediaFile(firstMediaFileName); + await umbracoApi.media.ensureNameNotExists(secondMediaFileName); + 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); +}); \ No newline at end of file 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 ea45f76b42..3e25adf754 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Media/Media.spec.ts @@ -110,7 +110,7 @@ test('can create a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderCreated); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.media.isTreeItemVisible(folderName); expect(await umbracoApi.media.doesNameExist(folderName)).toBeTruthy(); @@ -152,7 +152,7 @@ test('can create a folder in a folder', async ({umbracoApi, umbracoUi}) => { await umbracoUi.media.clickSaveButton(); // Assert - await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.folderCreated); + await umbracoUi.media.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); await umbracoUi.media.isTreeItemVisible(parentFolderName); await umbracoUi.media.clickMediaCaretButtonForName(parentFolderName); await umbracoUi.media.isTreeItemVisible(folderName); From b1d9085c8316cf2b7c5437d8e89fbfe37223235a Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 28 Oct 2024 11:11:28 +0100 Subject: [PATCH 73/95] V14 QA Added user groups acceptance tests (#17344) * Added tests for userGroup * Clean up * Updated userGroup tests * Updated tests * Updated tests * Cleane up * Cleaned up * Bumped versions * Run user tests * Cleaned up * Added method for checking if the document tree is empty * Bumped version * Reverted --- .../{ => User}/ContentStartNodes.spec.ts | 32 +- .../{ => User}/MediaStartNodes.spec.ts | 9 +- .../Permissions/{ => User}/UICulture.spec.ts | 9 +- .../UserGroup/ContentStartNodes.spec.ts | 92 ++++ .../Permissions/UserGroup/Languages.spec.ts | 135 +++++ .../Permissions/UserGroup/Sections.spec.ts | 51 ++ .../tests/DefaultConfig/Users/User.spec.ts | 7 +- .../DefaultConfig/Users/UserGroups.spec.ts | 474 ++++++++++++++++++ 8 files changed, 772 insertions(+), 37 deletions(-) rename tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/{ => User}/ContentStartNodes.spec.ts (85%) rename tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/{ => User}/MediaStartNodes.spec.ts (97%) rename tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/{ => User}/UICulture.spec.ts (93%) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts similarity index 85% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts index 24b50a707e..e4ab499479 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/ContentStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/ContentStartNodes.spec.ts @@ -1,29 +1,21 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -import {expect} from '@playwright/test'; -const testUser = { - name: 'Test User', - email: 'verySecureEmail@123.test', - password: 'verySecurePassword123', -}; +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; - -let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; - -let rootDocumentId = null; -let childDocumentOneId = null; const rootDocumentName = 'RootDocument'; const childDocumentOneName = 'ChildDocumentOne'; const childDocumentTwoName = 'ChildDocumentTwo'; - -let userGroupId = null; +let rootDocumentId = null; +let childDocumentOneId = null; test.beforeEach(async ({umbracoApi}) => { await umbracoApi.documentType.ensureNameNotExists(rootDocumentTypeName); @@ -59,10 +51,10 @@ test('can see root start node and children', async ({umbracoApi, umbracoUi}) => await umbracoUi.user.goToSection(ConstantHelper.sections.content, false); // Assert - await umbracoUi.content.isContentVisible(rootDocumentName); + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); - await umbracoUi.content.isChildContentVisible(rootDocumentName, childDocumentOneName); - await umbracoUi.content.isChildContentVisible(rootDocumentName, childDocumentTwoName); + 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}) => { @@ -75,12 +67,12 @@ test('can see parent of start node but not access it', async ({umbracoApi, umbra await umbracoUi.user.goToSection(ConstantHelper.sections.content, false); // Assert - await umbracoUi.content.isContentVisible(rootDocumentName); + await umbracoUi.content.isContentInTreeVisible(rootDocumentName); await umbracoUi.content.goToContentWithName(rootDocumentName); await umbracoUi.content.isTextWithMessageVisible('The authenticated user do not have access to this resource'); await umbracoUi.content.clickCaretButtonForContentName(rootDocumentName); - await umbracoUi.content.isChildContentVisible(rootDocumentName, childDocumentOneName); - await umbracoUi.content.isChildContentVisible(rootDocumentName, childDocumentTwoName, false); + 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}) => { @@ -93,5 +85,5 @@ test('can not see any content when no start nodes specified', async ({umbracoApi await umbracoUi.user.goToSection(ConstantHelper.sections.content, false); // Assert - await umbracoUi.content.isContentVisible(rootDocumentName, false); + await umbracoUi.content.isDocumentTreeEmpty(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts similarity index 97% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts index f9dec39020..573750f08b 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/MediaStartNodes.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/MediaStartNodes.spec.ts @@ -1,16 +1,11 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -const testUser = { - name: 'Test User', - email: 'verySecureEmail@123.test', - password: 'verySecurePassword123', -}; +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; const userGroupName = 'TestUserGroup'; let userGroupId = null; -let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; - let rootFolderId = null; let childFolderOneId = null; const rootFolderName = 'RootFolder'; diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/UICulture.spec.ts similarity index 93% rename from tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts rename to tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/UICulture.spec.ts index 5d94160d4a..ceac7890f7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UICulture.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/User/UICulture.spec.ts @@ -1,14 +1,9 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -const testUser = { - name: 'Test User', - email: 'verySecureEmail@123.test', - password: 'verySecurePassword123', -}; +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; const userGroupName = 'TestUserGroup'; - -let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; let userGroupId = null; test.beforeEach(async ({umbracoApi}) => { 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 0000000000..df20ec0397 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentStartNodes.spec.ts @@ -0,0 +1,92 @@ +import {ConstantHelper, 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.isTextWithMessageVisible('The authenticated user do not have access to this resource'); + 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/Languages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts new file mode 100644 index 0000000000..7b535fb067 --- /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/Sections.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts new file mode 100644 index 0000000000..0932ab7f62 --- /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 61977241e7..427693c8a8 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts @@ -424,7 +424,7 @@ test('can disable a user', async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickConfirmDisableButton(); // Assert - await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.userDisabled); expect(umbracoUi.user.isUserDisabledTextVisible()).toBeTruthy(); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.state).toBe(disabledStatus); @@ -444,7 +444,8 @@ test('can enable a user', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickConfirmEnableButton(); // Assert - await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.userDisabled); + // 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); @@ -480,7 +481,7 @@ test('can remove an avatar from a user', async ({umbracoApi, umbracoUi}) => { await umbracoUi.user.clickRemovePhotoButton(); // Assert - await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.user.doesSuccessNotificationHaveText(NotificationConstantHelper.success.avatarDeleted); const userData = await umbracoApi.user.getByName(nameOfTheUser); expect(userData.avatarUrls).toHaveLength(0); }); 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 0000000000..dd5676cab0 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts @@ -0,0 +1,474 @@ +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 ({umbracoApi, umbracoUi}) => { + // Act + await umbracoUi.userGroup.clickUserGroupsTabButton(); + await umbracoUi.userGroup.clickCreateUserGroupButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + await umbracoUi.userGroup.clickUserGroupWithName(userGroupName); + + // Act + await umbracoUi.userGroup.clickDeleteButton(); + await umbracoUi.userGroup.clickConfirmToDeleteButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); + expect(await umbracoApi.userGroup.doesNameExist(userGroupName)).toBeFalsy(); + await umbracoUi.userGroup.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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.clickUserGroupsTabButton(); + 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); +}); From 2d027ce9a1e1a4a868f2bf2995af511e8dff44b5 Mon Sep 17 00:00:00 2001 From: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:09:37 +0700 Subject: [PATCH 74/95] V14 QA Added acceptance tests for rendering content with content picker (#17378) * Added tests for rendering content with content picker * Bumped version * Make all the tests for rendering content run in the pipeline * Reverted --- .../package-lock.json | 8 ++-- .../Umbraco.Tests.AcceptanceTest/package.json | 2 +- .../RenderingContentWithContentPicker.spec.ts | 41 +++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithContentPicker.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index ce584dba6f..75883bf48c 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.23", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.95", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.96", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -63,9 +63,9 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "2.0.0-beta.95", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.95.tgz", - "integrity": "sha512-9pJrC/4d4O/TKSyHk0n12ZBFi3lRJd6+kRsngK1eCqcHjVqB7HTDWjsL7KS+1bWzVqFvJgbCVezt2eR+GiWBEA==", + "version": "2.0.0-beta.96", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.96.tgz", + "integrity": "sha512-kij2xWXWgrwnqa3dU8ErWd7tFAisewFpVRGHM6tI1LSa/z6/OhGmHJqhk3rIm2bCb/eh2WMQH40x6t5OC8YpOw==", "dependencies": { "@umbraco/json-models-builders": "2.0.23", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 85ef6be6d7..1696dafbee 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.23", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.95", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.96", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" 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 0000000000..0c87cb9911 --- /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); +}); From 2d4230c001777ea51851ffcec64b976840132207 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 8 Nov 2024 08:58:38 +0100 Subject: [PATCH 75/95] Include create date in audit item (#17447) --- src/Umbraco.Core/Models/AuditItem.cs | 15 ++++++++ .../Repositories/Implement/AuditRepository.cs | 12 +++--- .../Repositories/AuditRepositoryTest.cs | 38 ++++++++++++++++++- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Models/AuditItem.cs b/src/Umbraco.Core/Models/AuditItem.cs index bbfca724aa..7b4eff86e6 100644 --- a/src/Umbraco.Core/Models/AuditItem.cs +++ b/src/Umbraco.Core/Models/AuditItem.cs @@ -7,6 +7,21 @@ public sealed class AuditItem : EntityBase, IAuditItem /// /// Initializes a new instance of the class. /// + public AuditItem(int objectId, AuditType type, int userId, string? entityType, DateTime createDate, string? comment = null, string? parameters = null) + { + DisableChangeTracking(); + + Id = objectId; + Comment = comment; + AuditType = type; + UserId = userId; + EntityType = entityType; + Parameters = parameters; + CreateDate = createDate; + + EnableChangeTracking(); + } + public AuditItem(int objectId, AuditType type, int userId, string? entityType, string? comment = null, string? parameters = null) { DisableChangeTracking(); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs index f11e17a236..09fa3d1029 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -29,7 +29,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe List? dtos = Database.Fetch(sql); - return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Datestamp, x.Comment, x.Parameters)).ToList(); } public void CleanLogs(int maximumAgeOfLogsInMinutes) @@ -104,7 +104,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe totalRecords = page.TotalItems; var items = page.Items.Select( - dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList(); + dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Datestamp, dto.Comment, dto.Parameters)).ToList(); // map the DateStamp for (var i = 0; i < items.Count; i++) @@ -144,12 +144,12 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe protected override IAuditItem? PerformGet(int id) { Sql sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.Where(GetBaseWhereClause(), new { id = id }); LogDto? dto = Database.First(sql); return dto == null ? null - : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters); + : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Datestamp, dto.Comment, dto.Parameters); } protected override IEnumerable PerformGetAll(params int[]? ids) => throw new NotImplementedException(); @@ -162,7 +162,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe List? dtos = Database.Fetch(sql); - return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters)).ToList(); + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Datestamp, x.Comment, x.Parameters)).ToList(); } protected override Sql GetBaseQuery(bool isCount) @@ -184,7 +184,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe return sql; } - protected override string GetBaseWhereClause() => "id = @id"; + protected override string GetBaseWhereClause() => "umbracoLog.id = @id"; protected override IEnumerable GetDeleteClauses() => throw new NotImplementedException(); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs index 57f484adf2..8e7eed1f28 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs @@ -1,11 +1,11 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Linq; using Microsoft.Extensions.Logging; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; @@ -24,6 +24,10 @@ public class AuditRepositoryTest : UmbracoIntegrationTest private ILogger _logger; + private IAuditRepository AuditRepository => GetRequiredService(); + + private IAuditItem GetAuditItem(int id) => new AuditItem(id, AuditType.System, -1, UmbracoObjectTypes.Document.GetName(), "This is a System audit trail"); + [Test] public void Can_Add_Audit_Entry() { @@ -40,6 +44,38 @@ public class AuditRepositoryTest : UmbracoIntegrationTest } } + [Test] + public void Has_Create_Date_When_Get_By_Id() + { + using var scope = ScopeProvider.CreateScope(); + + AuditRepository.Save(GetAuditItem(1)); + var auditEntry = AuditRepository.Get(1); + Assert.That(auditEntry.CreateDate, Is.Not.EqualTo(default(DateTime))); + } + + [Test] + public void Has_Create_Date_When_Get_By_Query() + { + using var scope = ScopeProvider.CreateScope(); + + AuditRepository.Save(GetAuditItem(1)); + var auditEntry = AuditRepository.Get(AuditType.System, ScopeProvider.CreateQuery().Where(x => x.Id == 1)).FirstOrDefault(); + Assert.That(auditEntry, Is.Not.Null); + Assert.That(auditEntry.CreateDate, Is.Not.EqualTo(default(DateTime))); + } + + [Test] + public void Has_Create_Date_When_Get_By_Paged_Query() + { + using var scope = ScopeProvider.CreateScope(); + + AuditRepository.Save(GetAuditItem(1)); + var auditEntry = AuditRepository.GetPagedResultsByQuery(ScopeProvider.CreateQuery().Where(x => x.Id == 1),0, 10, out long total, Direction.Ascending, null, null).FirstOrDefault(); + Assert.That(auditEntry, Is.Not.Null); + Assert.That(auditEntry.CreateDate, Is.Not.EqualTo(default(DateTime))); + } + [Test] public void Get_Paged_Items() { From 0acdd2685090b3da362db7e475035e7189f847fb Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Fri, 8 Nov 2024 12:18:13 +0100 Subject: [PATCH 76/95] Made some stylesheet endpoints available for document/media/member related actions (#17442) --- .../Controllers/Stylesheet/CreateStylesheetController.cs | 3 +++ .../Controllers/Stylesheet/DeleteStylesheetController.cs | 3 +++ .../Controllers/Stylesheet/RenameStylesheetController.cs | 3 +++ .../Controllers/Stylesheet/StylesheetControllerBase.cs | 2 +- .../Controllers/Stylesheet/UpdateStylesheetController.cs | 3 +++ .../BackOfficeAuthPolicyBuilderExtensions.cs | 1 + src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs | 1 + 7 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs index 5471af2f1d..165533f287 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/CreateStylesheetController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)] public class CreateStylesheetController : StylesheetControllerBase { private readonly IStylesheetService _stylesheetService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs index 51c6ebb3cc..3dcdb92bbd 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs @@ -1,14 +1,17 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)] public class DeleteStylesheetController : StylesheetControllerBase { private readonly IStylesheetService _stylesheetService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs index 6480617918..e1ce77717d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/RenameStylesheetController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)] public class RenameStylesheetController : StylesheetControllerBase { private readonly IStylesheetService _stylesheetService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs index f36b707424..9828cefa10 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/StylesheetControllerBase.cs @@ -11,7 +11,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; [VersionedApiBackOfficeRoute($"{Constants.UdiEntityType.Stylesheet}")] [ApiExplorerSettings(GroupName = "Stylesheet")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)] +[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheetsOrDocumentOrMediaOrMember)] public class StylesheetControllerBase : FileSystemManagementControllerBase { protected IActionResult StylesheetOperationStatusResult(StylesheetOperationStatus status) => diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs index 7c54170b89..377fef87d1 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Extensions; @@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.Stylesheet; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessStylesheets)] public class UpdateStylesheetController : StylesheetControllerBase { private readonly IStylesheetService _stylesheetService; diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs index 3a2c860ade..1cc8a95197 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -91,6 +91,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheets, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessStylesheetsOrDocumentOrMediaOrMember, Constants.Applications.Settings, Constants.Applications.Content, Constants.Applications.Media, Constants.Applications.Members); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessTemplates, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessWebhooks, Constants.Applications.Settings); diff --git a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs index 9b54a3912a..fd811161c7 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 TreeAccessStylesheetsOrDocumentOrMediaOrMember = nameof(TreeAccessStylesheetsOrDocumentOrMediaOrMember); // other public const string DictionaryPermissionByResource = nameof(DictionaryPermissionByResource); From a5479bc96ea3ddb6d2078152d008a10457dcb653 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:43:57 +0100 Subject: [PATCH 77/95] V14 QA members section user tests (#17448) * Added tests for the members * Cleaned up tests * Bumped version * Removed skip --- .../package-lock.json | 20 ++-- .../Umbraco.Tests.AcceptanceTest/package.json | 4 +- .../UserGroup/MemberSection.spec.ts | 95 +++++++++++++++++++ 3 files changed, 108 insertions(+), 11 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MemberSection.spec.ts diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index 75883bf48c..a0d2fbdc74 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^2.0.23", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.96", + "@umbraco/json-models-builders": "^2.0.25", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.98", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -55,19 +55,21 @@ } }, "node_modules/@umbraco/json-models-builders": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.23.tgz", - "integrity": "sha512-48TgQnrdxQ2Oi/NintzgYvVRnlX3JXKq505leuWATo9AH3ffuzR+g7hXoTC/Us9ms5BjTTXssLBwzlgCyHJTJQ==", + "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": "2.0.0-beta.96", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.96.tgz", - "integrity": "sha512-kij2xWXWgrwnqa3dU8ErWd7tFAisewFpVRGHM6tI1LSa/z6/OhGmHJqhk3rIm2bCb/eh2WMQH40x6t5OC8YpOw==", + "version": "2.0.0-beta.98", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-2.0.0-beta.98.tgz", + "integrity": "sha512-gOQoLx5aSa5G0U4aFyp3Hd8xGlnM/x1th0Yv6/zYktZNwzBBfWIhMOBWulAzTNhdvfNcKjuBQCg2opQ4ZZNsew==", + "license": "MIT", "dependencies": { - "@umbraco/json-models-builders": "2.0.23", + "@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 1696dafbee..237bf0f3dd 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -20,8 +20,8 @@ "typescript": "^4.8.3" }, "dependencies": { - "@umbraco/json-models-builders": "^2.0.23", - "@umbraco/playwright-testhelpers": "^2.0.0-beta.96", + "@umbraco/json-models-builders": "^2.0.25", + "@umbraco/playwright-testhelpers": "^2.0.0-beta.98", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" 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 0000000000..eb5ee1f204 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MemberSection.spec.ts @@ -0,0 +1,95 @@ +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); +}); + +test('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); + + // Act + await umbracoUi.member.clickCreateButton(); + await umbracoUi.member.enterMemberName(memberName); + 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(); +}); + +test('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); +}); From 6acdf21eabb19457f45abaa5464d9846dab2c48b Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 11 Nov 2024 09:45:22 +0100 Subject: [PATCH 78/95] V14 QA user groups permissions tests (#17429) * Added tests for userGroup * Clean up * Updated userGroup tests * Updated tests * Updated tests * Cleane up * Cleaned up * Bumped versions * Run user tests * Cleaned up * Added permission tests * Added tests, not done * Updated tests * Fixed tests * Cleaned up * Bumped version * More cleanup * Run user tests * Added wait * Added tests and cleaned up naming * Bumped versions * Reverted smokeTest command --- .../DefaultConfig/Content/Content.spec.ts | 3 +- .../Permissions/User/MediaStartNodes.spec.ts | 2 +- .../DefaultPermissionsInContent.spec.ts | 619 ++++++++++++++++++ .../UserGroup/MediaStartNodes.spec.ts | 85 +++ .../DefaultConfig/Users/UserGroups.spec.ts | 2 +- 5 files changed, 707 insertions(+), 4 deletions(-) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/DefaultPermissionsInContent.spec.ts create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts 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 693762a0ec..07f16cab9f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/Content.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/Content.spec.ts @@ -147,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); 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 index 573750f08b..cf545b025b 100644 --- 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 @@ -21,7 +21,7 @@ test.beforeEach(async ({umbracoApi}) => { rootFolderId = await umbracoApi.media.createDefaultMediaFolder(rootFolderName); childFolderOneId = await umbracoApi.media.createDefaultMediaFolderAndParentId(childFolderOneName, rootFolderId); await umbracoApi.media.createDefaultMediaFolderAndParentId(childFolderTwoName, rootFolderId); - userGroupId = await umbracoApi.userGroup.createUserGroupWithMediaSection(userGroupName); + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithMediaSection(userGroupName); }); test.afterEach(async ({umbracoApi}) => { 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 0000000000..900081db82 --- /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.clickExactTrashButton(); + 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/MediaStartNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts new file mode 100644 index 0000000000..61a99709e3 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/MediaStartNodes.spec.ts @@ -0,0 +1,85 @@ +import {ConstantHelper, 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.isMediaVisible(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.isMediaVisible(rootFolderName); + await umbracoUi.waitForTimeout(500); + await umbracoUi.media.goToMediaWithName(rootFolderName); + await umbracoUi.media.isTextWithMessageVisible('The authenticated user do not have access to this resource'); + 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.isMediaVisible(rootFolderName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts index dd5676cab0..502b40eb8f 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/UserGroups.spec.ts @@ -64,7 +64,7 @@ test('can create an empty user group', async ({umbracoApi, umbracoUi}) => { // Checks if the user group was created in the UI as well await umbracoUi.userGroup.clickUserGroupsTabButton(); await umbracoUi.userGroup.isUserGroupWithNameVisible(userGroupName); -}) +}); test('can rename a user group', async ({umbracoApi, umbracoUi}) => { // Arrange From 7010ff1d2684b0abf9233a9d0618e7397283df8e Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:47:41 +0100 Subject: [PATCH 79/95] V14 QA added rich text editor with stylesheet test (#17449) * Rte tests * Cleaned up * Bumped version * Added ensure not exists --- .../ContentWithRichTextEditor.spec.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentWithRichTextEditor.spec.ts 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 0000000000..df9655388a --- /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(); +}); From 9edd21a3b043384221956d539108e1a58a3aeeba Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Mon, 11 Nov 2024 09:02:56 +0100 Subject: [PATCH 80/95] Made some membertype endpoints available for member related actions (#17440) Co-authored-by: nikolajlauridsen --- .../MemberType/AvailableCompositionMemberTypeController.cs | 3 +++ .../MemberType/CompositionReferenceMemberTypeController.cs | 3 +++ .../MemberType/ConfigurationMemberTypeController.cs | 1 + .../Controllers/MemberType/CopyMemberTypeController.cs | 3 +++ .../Controllers/MemberType/CreateMemberTypeController.cs | 3 +++ .../Controllers/MemberType/DeleteMemberTypeController.cs | 3 +++ .../Controllers/MemberType/MemberTypeControllerBase.cs | 2 +- .../MemberType/Tree/MemberTypeTreeControllerBase.cs | 2 +- .../Controllers/MemberType/UpdateMemberTypeController.cs | 3 +++ .../BackOfficeAuthPolicyBuilderExtensions.cs | 1 + src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs | 1 + 11 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/AvailableCompositionMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/AvailableCompositionMemberTypeController.cs index 277ef4c4f3..a43e662b76 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/AvailableCompositionMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/AvailableCompositionMemberTypeController.cs @@ -1,14 +1,17 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; using Umbraco.Cms.Api.Management.ViewModels.MemberType; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services.ContentTypeEditing; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class AvailableCompositionMemberTypeController : MemberTypeControllerBase { private readonly IMemberTypeEditingService _memberTypeEditingService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CompositionReferenceMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CompositionReferenceMemberTypeController.cs index 55df93ef63..c20037319a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CompositionReferenceMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CompositionReferenceMemberTypeController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.ViewModels.MemberType; @@ -6,10 +7,12 @@ using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class CompositionReferenceMemberTypeController : MemberTypeControllerBase { private readonly IMemberTypeService _memberTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs index 141dbd80ee..2dacf50fef 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/ConfigurationMemberTypeController.cs @@ -9,6 +9,7 @@ using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class ConfigurationMemberTypeController : MemberTypeControllerBase { private readonly IConfigurationPresentationFactory _configurationPresentationFactory; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CopyMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CopyMemberTypeController.cs index b19bfcd3ce..e0d24d0973 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CopyMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CopyMemberTypeController.cs @@ -1,14 +1,17 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class CopyMemberTypeController : MemberTypeControllerBase { private readonly IMemberTypeService _memberTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CreateMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CreateMemberTypeController.cs index 2439118ac7..8ab82ecb07 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CreateMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/CreateMemberTypeController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -9,10 +10,12 @@ using Umbraco.Cms.Core.Models.ContentTypeEditing; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services.ContentTypeEditing; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class CreateMemberTypeController : MemberTypeControllerBase { private readonly IMemberTypeEditingPresentationFactory _memberTypeEditingPresentationFactory; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/DeleteMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/DeleteMemberTypeController.cs index bb3e87e7bc..a79bb4c2a3 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/DeleteMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/DeleteMemberTypeController.cs @@ -1,13 +1,16 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class DeleteMemberTypeController : MemberTypeControllerBase { private readonly IMemberTypeService _memberTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/MemberTypeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/MemberTypeControllerBase.cs index 73ad7b6374..7b4395382d 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/MemberTypeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/MemberTypeControllerBase.cs @@ -10,7 +10,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [VersionedApiBackOfficeRoute(Constants.UdiEntityType.MemberType)] [ApiExplorerSettings(GroupName = "Member Type")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)] public abstract class MemberTypeControllerBase : ManagementApiControllerBase { protected IActionResult OperationStatusResult(ContentTypeOperationStatus status) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs index d2ef0cf0e4..ef9e8db235 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/Tree/MemberTypeTreeControllerBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Api.Management.Controllers.MemberType.Tree; [VersionedApiBackOfficeRoute($"{Constants.Web.RoutePath.Tree}/{Constants.UdiEntityType.MemberType}")] [ApiExplorerSettings(GroupName = "Member Type")] -[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMembersOrMemberTypes)] public class MemberTypeTreeControllerBase : NamedEntityTreeControllerBase { private readonly IMemberTypeService _memberTypeService; diff --git a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/UpdateMemberTypeController.cs b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/UpdateMemberTypeController.cs index 2915662da3..f3be972803 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/MemberType/UpdateMemberTypeController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/MemberType/UpdateMemberTypeController.cs @@ -1,4 +1,5 @@ using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Umbraco.Cms.Api.Management.Factories; @@ -10,10 +11,12 @@ using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.ContentTypeEditing; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Web.Common.Authorization; namespace Umbraco.Cms.Api.Management.Controllers.MemberType; [ApiVersion("1.0")] +[Authorize(Policy = AuthorizationPolicies.TreeAccessMemberTypes)] public class UpdateMemberTypeController : MemberTypeControllerBase { private readonly IMemberTypeEditingPresentationFactory _memberTypeEditingPresentationFactory; diff --git a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs index 1cc8a95197..c79e64e4f1 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -87,6 +87,7 @@ internal static class BackOfficeAuthPolicyBuilderExtensions AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMediaOrMediaTypes, Constants.Applications.Media, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberGroups, Constants.Applications.Members); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMemberTypes, Constants.Applications.Settings); + AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessMembersOrMemberTypes, Constants.Applications.Settings, Constants.Applications.Members); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessPartialViews, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessRelationTypes, Constants.Applications.Settings); AddAllowedApplicationsPolicy(AuthorizationPolicies.TreeAccessScripts, Constants.Applications.Settings); diff --git a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs index fd811161c7..b27f4a85b2 100644 --- a/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs +++ b/src/Umbraco.Web.Common/Authorization/AuthorizationPolicies.cs @@ -54,6 +54,7 @@ public static class AuthorizationPolicies public const string TreeAccessDictionaryOrTemplates = nameof(TreeAccessDictionaryOrTemplates); public const string TreeAccessDocumentOrMediaOrContentTypes = nameof(TreeAccessDocumentOrMediaOrContentTypes); public const string TreeAccessStylesheetsOrDocumentOrMediaOrMember = nameof(TreeAccessStylesheetsOrDocumentOrMediaOrMember); + public const string TreeAccessMembersOrMemberTypes = nameof(TreeAccessMembersOrMemberTypes); // other public const string DictionaryPermissionByResource = nameof(DictionaryPermissionByResource); From d7b98a2f00c82b95dd50e5e35160b8929e7dc3b6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 11 Nov 2024 09:47:07 +0100 Subject: [PATCH 81/95] Use catch all for urls what ends with a path (#17468) (cherry picked from commit 3c0f7a03bae492899e3dab8965726a3e58c4b790) --- .../Controllers/PartialView/ByPathPartialViewController.cs | 2 +- .../Controllers/PartialView/DeletePartialViewController.cs | 2 +- .../PartialView/Folder/ByPathPartialViewFolderController.cs | 2 +- .../PartialView/Folder/DeletePartialViewFolderController.cs | 2 +- .../Controllers/PartialView/UpdatePartialViewController.cs | 2 +- .../Controllers/Script/ByPathScriptController.cs | 2 +- .../Controllers/Script/DeleteScriptController.cs | 2 +- .../Controllers/Script/Folder/ByPathScriptFolderController.cs | 2 +- .../Controllers/Script/Folder/DeleteScriptFolderController.cs | 2 +- .../Controllers/Script/UpdateScriptController.cs | 2 +- .../Controllers/Stylesheet/ByPathStylesheetController.cs | 2 +- .../Controllers/Stylesheet/DeleteStylesheetController.cs | 2 +- .../Stylesheet/Folder/ByPathStylesheetFolderController.cs | 2 +- .../Stylesheet/Folder/DeleteStylesheetFolderController.cs | 2 +- .../Controllers/Stylesheet/UpdateStylesheetController.cs | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs index 3fdae52115..bbf1dec98f 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/ByPathPartialViewController.cs @@ -23,7 +23,7 @@ public class ByPathPartialViewController : PartialViewControllerBase _mapper = mapper; } - [HttpGet("{path}")] + [HttpGet("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PartialViewResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs index a72c8129b5..b44990b41e 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/DeletePartialViewController.cs @@ -22,7 +22,7 @@ public class DeletePartialViewController : PartialViewControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpDelete("{path}")] + [HttpDelete("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs index df15c726b5..727f4d1d68 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/ByPathPartialViewFolderController.cs @@ -22,7 +22,7 @@ public class ByPathPartialViewFolderController : PartialViewFolderControllerBase _mapper = mapper; } - [HttpGet("{path}")] + [HttpGet("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(PartialViewFolderResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs index 121cb1dae7..41cf9548d5 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/Folder/DeletePartialViewFolderController.cs @@ -15,7 +15,7 @@ public class DeletePartialViewFolderController : PartialViewFolderControllerBase public DeletePartialViewFolderController(IPartialViewFolderService partialViewFolderService) => _partialViewFolderService = partialViewFolderService; - [HttpDelete("{path}")] + [HttpDelete("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs index a935f87a80..e460b0adf0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/PartialView/UpdatePartialViewController.cs @@ -29,7 +29,7 @@ public class UpdatePartialViewController : PartialViewControllerBase _mapper = mapper; } - [HttpPut("{path}")] + [HttpPut("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs index 67a3b605a7..60c5fe644a 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/ByPathScriptController.cs @@ -23,7 +23,7 @@ public class ByPathScriptController : ScriptControllerBase _mapper = mapper; } - [HttpGet("{path}")] + [HttpGet("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ScriptResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs index 74dc1554e8..94c2e0a1c0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/DeleteScriptController.cs @@ -22,7 +22,7 @@ public class DeleteScriptController : ScriptControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpDelete("{path}")] + [HttpDelete("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs index 84543bffe4..698c94f685 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/ByPathScriptFolderController.cs @@ -22,7 +22,7 @@ public class ByPathScriptFolderController : ScriptFolderControllerBase _mapper = mapper; } - [HttpGet("{path}")] + [HttpGet("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(ScriptFolderResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs index b5fd5022b7..48d97e9b4b 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/Folder/DeleteScriptFolderController.cs @@ -15,7 +15,7 @@ public class DeleteScriptFolderController : ScriptFolderControllerBase public DeleteScriptFolderController(IScriptFolderService scriptFolderService) => _scriptFolderService = scriptFolderService; - [HttpDelete("{path}")] + [HttpDelete("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs index 87ff3502ba..a18f82ebc8 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Script/UpdateScriptController.cs @@ -29,7 +29,7 @@ public class UpdateScriptController : ScriptControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpPut("{path}")] + [HttpPut("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs index a7e3a6139f..87aec60133 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/ByPathStylesheetController.cs @@ -23,7 +23,7 @@ public class ByPathStylesheetController : StylesheetControllerBase _umbracoMapper = umbracoMapper; } - [HttpGet("{path}")] + [HttpGet("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(StylesheetResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs index 3dcdb92bbd..9996ddeae0 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/DeleteStylesheetController.cs @@ -25,7 +25,7 @@ public class DeleteStylesheetController : StylesheetControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpDelete("{path}")] + [HttpDelete("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs index d5d0cca84f..95c7d853ab 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/ByPathStylesheetFolderController.cs @@ -24,7 +24,7 @@ public class ByPathStylesheetFolderController : StylesheetFolderControllerBase _mapper = mapper; } - [HttpGet("{path}")] + [HttpGet("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(typeof(StylesheetFolderResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs index 38760b34e7..deb38f0865 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/Folder/DeleteStylesheetFolderController.cs @@ -15,7 +15,7 @@ public class DeleteStylesheetFolderController : StylesheetFolderControllerBase public DeleteStylesheetFolderController(IStylesheetFolderService stylesheetFolderService) => _stylesheetFolderService = stylesheetFolderService; - [HttpDelete("{path}")] + [HttpDelete("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] diff --git a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs index 377fef87d1..7000ca7b40 100644 --- a/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs +++ b/src/Umbraco.Cms.Api.Management/Controllers/Stylesheet/UpdateStylesheetController.cs @@ -32,7 +32,7 @@ public class UpdateStylesheetController : StylesheetControllerBase _backOfficeSecurityAccessor = backOfficeSecurityAccessor; } - [HttpPut("{path}")] + [HttpPut("{*path}")] [MapToApiVersion("1.0")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] From 33eb4dd5a4e14502bd9c87f9a3a6df0b97ec2737 Mon Sep 17 00:00:00 2001 From: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Date: Mon, 18 Nov 2024 08:30:50 +0100 Subject: [PATCH 82/95] V14 QA updated playwright config (#17544) * Only run windows SQL Server * Updated timeout and retry count * Updated condition * Fixed indentation * Skipped --- build/azure-pipelines.yml | 14 ++++---- .../playwright.config.ts | 4 +-- .../Packages/CreatedPackages.spec.ts | 32 ++++++++++--------- .../Packages/InstalledPackages.spec.ts | 2 +- .../Packages/PackagesPackages.spec.ts | 2 +- 5 files changed, 28 insertions(+), 26 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index 752f18fd0d..24c1ac4d1c 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -5,8 +5,8 @@ parameters: displayName: Run SQL Server Integration Tests type: boolean default: false - - name: sqlServerAcceptanceTests - displayName: Run SQL Server Acceptance Tests + - name: sqlServerLinuxAcceptanceTests + displayName: Run SQL Server Linux Acceptance Tests type: boolean default: false - name: myGetDeploy @@ -557,17 +557,17 @@ stages: - job: displayName: E2E Tests (SQL Server) - condition: eq(${{parameters.sqlServerAcceptanceTests}}, True) variables: # Connection string CONNECTIONSTRINGS__UMBRACODBDSN: Data Source=(localdb)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\Umbraco.mdf;Integrated Security=True 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/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts index c0b1d07107..021c938d02 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: 1, // We don't want to run parallel, as tests might differ in state workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts index 244aaa3f4b..0d9a1f3e2e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts @@ -4,6 +4,8 @@ import * as fs from 'fs'; const packageName = 'TestPackage'; +// TODO: unskip tests when they work, currently we run into a weird issue when the playwright hits the Iframe of the package marketplace. + test.beforeEach(async ({umbracoApi, umbracoUi}) => { await umbracoApi.package.ensureNameNotExists(packageName); await umbracoUi.goToBackOffice(); @@ -15,7 +17,7 @@ test.afterEach(async ({umbracoApi}) => { await umbracoApi.package.ensureNameNotExists(packageName); }); -test('can create a empty package', {tag: '@smoke'}, async ({umbracoUi}) => { +test.skip('can create a empty package', {tag: '@smoke'}, async ({umbracoUi}) => { // Act await umbracoUi.package.clickCreatePackageButton(); await umbracoUi.package.enterPackageName(packageName); @@ -27,7 +29,7 @@ test('can create a empty package', {tag: '@smoke'}, async ({umbracoUi}) => { await umbracoUi.package.isPackageNameVisible(packageName); }); -test('can update package name', async ({umbracoApi, umbracoUi}) => { +test.skip('can update package name', async ({umbracoApi, umbracoUi}) => { // Arrange const wrongPackageName = 'WrongPackageName'; await umbracoApi.package.ensureNameNotExists(wrongPackageName); @@ -48,7 +50,7 @@ test('can update package name', async ({umbracoApi, umbracoUi}) => { expect(umbracoApi.package.doesNameExist(packageName)).toBeTruthy(); }); -test('can delete a package', async ({umbracoApi, umbracoUi}) => { +test.skip('can delete a package', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.package.createEmptyPackage(packageName); await umbracoUi.reloadPage(); @@ -64,7 +66,7 @@ test('can delete a package', async ({umbracoApi, umbracoUi}) => { expect(await umbracoApi.package.doesNameExist(packageName)).toBeFalsy(); }); -test('can create a package with content', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with content', async ({umbracoApi, umbracoUi}) => { // Arrange const documentTypeName = 'TestDocumentType'; const documentName = 'TestDocument'; @@ -90,7 +92,7 @@ test('can create a package with content', async ({umbracoApi, umbracoUi}) => { await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); -test('can create a package with media', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with media', async ({umbracoApi, umbracoUi}) => { // Arrange const mediaName = 'TestMedia'; await umbracoApi.media.ensureNameNotExists(mediaName); @@ -114,7 +116,7 @@ test('can create a package with media', async ({umbracoApi, umbracoUi}) => { await umbracoApi.media.ensureNameNotExists(mediaName); }); -test('can create a package with document types', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with document types', async ({umbracoApi, umbracoUi}) => { // Arrange const documentTypeName = 'TestDocumentType'; await umbracoApi.documentType.ensureNameNotExists(documentTypeName); @@ -138,7 +140,7 @@ test('can create a package with document types', async ({umbracoApi, umbracoUi}) await umbracoApi.documentType.ensureNameNotExists(documentTypeName); }); -test('can create a package with media types', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with media types', async ({umbracoApi, umbracoUi}) => { // Arrange const mediaTypeName = 'TestMediaType'; await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); @@ -162,7 +164,7 @@ test('can create a package with media types', async ({umbracoApi, umbracoUi}) => await umbracoApi.mediaType.ensureNameNotExists(mediaTypeName); }); -test('can create a package with languages', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with languages', async ({umbracoApi, umbracoUi}) => { // Arrange await umbracoApi.language.ensureNameNotExists('Danish'); const languageId = await umbracoApi.language.createDanishLanguage(); @@ -187,7 +189,7 @@ test('can create a package with languages', async ({umbracoApi, umbracoUi}) => { await umbracoApi.language.ensureNameNotExists(languageName); }); -test('can create a package with dictionary', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with dictionary', async ({umbracoApi, umbracoUi}) => { // Arrange const dictionaryName = 'TestDictionary'; const dictionaryId = await umbracoApi.dictionary.createDefaultDictionary(dictionaryName); @@ -210,7 +212,7 @@ test('can create a package with dictionary', async ({umbracoApi, umbracoUi}) => await umbracoApi.dictionary.ensureNameNotExists(dictionaryName); }); -test('can create a package with data types', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with data types', async ({umbracoApi, umbracoUi}) => { // Arrange const dataTypeName = 'TestDataType'; await umbracoApi.dataType.ensureNameNotExists(dataTypeName); @@ -234,7 +236,7 @@ test('can create a package with data types', async ({umbracoApi, umbracoUi}) => await umbracoApi.dataType.ensureNameNotExists(dataTypeName); }); -test('can create a package with templates', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with templates', async ({umbracoApi, umbracoUi}) => { // Arrange const templateName = 'TestTemplate'; await umbracoApi.template.ensureNameNotExists(templateName); @@ -258,7 +260,7 @@ test('can create a package with templates', async ({umbracoApi, umbracoUi}) => { await umbracoApi.template.ensureNameNotExists(templateName); }); -test('can create a package with stylesheets', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with stylesheets', async ({umbracoApi, umbracoUi}) => { // Arrange const stylesheetName = 'TestStylesheet.css'; await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); @@ -282,7 +284,7 @@ test('can create a package with stylesheets', async ({umbracoApi, umbracoUi}) => await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); }); -test('can create a package with scripts', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with scripts', async ({umbracoApi, umbracoUi}) => { // Arrange const scriptName = 'TestScripts.js'; await umbracoApi.script.ensureNameNotExists(scriptName); @@ -306,7 +308,7 @@ test('can create a package with scripts', async ({umbracoApi, umbracoUi}) => { await umbracoApi.script.ensureNameNotExists(scriptName); }); -test('can create a package with partial views', async ({umbracoApi, umbracoUi}) => { +test.skip('can create a package with partial views', async ({umbracoApi, umbracoUi}) => { // Arrange const partialViewName = 'TestPartialView.cshtml'; const partialViewId = await umbracoApi.partialView.createDefaultPartialView(partialViewName); @@ -329,7 +331,7 @@ test('can create a package with partial views', async ({umbracoApi, umbracoUi}) await umbracoApi.partialView.ensureNameNotExists(partialViewName); }); -test('can download a package', async ({umbracoApi, umbracoUi}) => { +test.skip('can download a package', async ({umbracoApi, umbracoUi}) => { // Arrange const packageId = await umbracoApi.package.createEmptyPackage(packageName); await umbracoUi.reloadPage(); 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 9164b496f5..fbd11c8cfd 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts @@ -1,6 +1,6 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -test('can see the umbraco package is installed', async ({umbracoUi}) => { +test.skip('can see the umbraco package is installed', async ({umbracoUi}) => { // Arrange await umbracoUi.goToBackOffice(); await umbracoUi.package.goToSection(ConstantHelper.sections.packages); 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 6d3c159e95..eb33d7b20d 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/PackagesPackages.spec.ts @@ -1,6 +1,6 @@ import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; -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); From e662468ecc0b0b862294eb02964cd9a0caccf5a8 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Fri, 15 Nov 2024 22:59:42 +0100 Subject: [PATCH 83/95] Set TinyMCE to readonly --- .../src/views/propertyeditors/rte/rte.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js index d499f47a9c..f2e6d78a77 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js @@ -276,7 +276,7 @@ // Readonly mode baseLineConfigObj.toolbar = vm.readonly ? false : baseLineConfigObj.toolbar; - baseLineConfigObj.readonly = vm.readonly ? 1 : baseLineConfigObj.readonly; + baseLineConfigObj.readonly = vm.readonly ? true : baseLineConfigObj.readonly; // We need to wait for DOM to have rendered before we can find the element by ID. $timeout(function () { From 1e9182cfa44ec1ed704454de0e1441013ada44d0 Mon Sep 17 00:00:00 2001 From: Mole Date: Tue, 19 Nov 2024 11:14:53 +0100 Subject: [PATCH 84/95] V14: Use decimal in slider property editor (#17568) * Allow SliderPropertyEditor to use decimals * Add tests * Boyscout unittest update --------- Co-authored-by: Sven Geusens --- .../PropertyEditors/SliderPropertyEditor.cs | 13 ++--- .../PropertyEditors/SliderValueEditorTests.cs | 47 ++++++++----------- 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs index 8321915677..b67e4af0a2 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/SliderPropertyEditor.cs @@ -1,6 +1,7 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Globalization; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; @@ -51,8 +52,8 @@ public class SliderPropertyEditor : DataEditor public override object? ToEditor(IProperty property, string? culture = null, string? segment = null) { - // value is stored as a string - either a single integer value - // or a two integer values separated by comma (for range sliders) + // value is stored as a string - either a single decimal value + // or a two decimal values separated by comma (for range sliders) var value = property.GetValue(culture, segment); if (value is not string stringValue) { @@ -61,7 +62,7 @@ public class SliderPropertyEditor : DataEditor var parts = stringValue.Split(Constants.CharArrays.Comma); var parsed = parts - .Select(s => int.TryParse(s, out var i) ? i : (int?)null) + .Select(s => decimal.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out var i) ? i : (decimal?)null) .Where(i => i != null) .Select(i => i!.Value) .ToArray(); @@ -78,11 +79,11 @@ public class SliderPropertyEditor : DataEditor internal class SliderRange { - public int From { get; set; } + public decimal From { get; set; } - public int To { get; set; } + public decimal To { get; set; } - public override string ToString() => From == To ? $"{From}" : $"{From},{To}"; + public override string ToString() => From == To ? $"{From.ToString(CultureInfo.InvariantCulture)}" : $"{From.ToString(CultureInfo.InvariantCulture)},{To.ToString(CultureInfo.InvariantCulture)}"; } } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs index 0850422598..6ec212c02e 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/SliderValueEditorTests.cs @@ -6,7 +6,6 @@ using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; using Umbraco.Cms.Infrastructure.Serialization; @@ -15,19 +14,11 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors; [TestFixture] public class SliderValueEditorTests { - // annoyingly we can't use decimals etc. in attributes, so we can't turn these into test cases :( - private List _invalidValues = new(); - - [SetUp] - public void SetUp() => _invalidValues = new List + public static object[] InvalidCaseData = new object[] { 123m, 123, -123, - 123.45d, - "123.45", - "1.234,56", - "1.2.3.4", "something", true, new object(), @@ -36,21 +27,19 @@ public class SliderValueEditorTests new GuidUdi(Constants.UdiEntityType.Document, Guid.NewGuid()) }; - [Test] - public void Can_Handle_Invalid_Values_From_Editor() + [TestCaseSource(nameof(InvalidCaseData))] + public void Can_Handle_Invalid_Values_From_Editor(object value) { - foreach (var value in _invalidValues) - { - var fromEditor = FromEditor(value); - Assert.IsNull(fromEditor, message: $"Failed for: {value}"); - } + var fromEditor = FromEditor(value); + Assert.IsNull(fromEditor); } [TestCase("1", 1)] [TestCase("0", 0)] [TestCase("-1", -1)] [TestCase("123456789", 123456789)] - public void Can_Parse_Single_Value_To_Editor(string value, int expected) + [TestCase("123.45", 123.45)] + public void Can_Parse_Single_Value_To_Editor(string value, decimal expected) { var toEditor = ToEditor(value) as SliderPropertyEditor.SliderPropertyValueEditor.SliderRange; Assert.IsNotNull(toEditor); @@ -62,7 +51,10 @@ public class SliderValueEditorTests [TestCase("0,0", 0, 0)] [TestCase("-1,-1", -1, -1)] [TestCase("10,123456789", 10, 123456789)] - public void Can_Parse_Range_Value_To_Editor(string value, int expectedFrom, int expectedTo) + [TestCase("1.234,56", 1.234, 56)] + [TestCase("4,6.234", 4, 6.234)] + [TestCase("10.45,15.3", 10.45, 15.3)] + public void Can_Parse_Range_Value_To_Editor(string value, decimal expectedFrom, decimal expectedTo) { var toEditor = ToEditor(value) as SliderPropertyEditor.SliderPropertyValueEditor.SliderRange; Assert.IsNotNull(toEditor); @@ -75,21 +67,22 @@ public class SliderValueEditorTests [TestCase(0, 0, "0")] [TestCase(-10, -10, "-10")] [TestCase(10, 123456789, "10,123456789")] - public void Can_Parse_Valid_Value_From_Editor(int from, int to, string expectedResult) + [TestCase(1.5, 1.5, "1.5")] + [TestCase(0, 0.5, "0,0.5")] + [TestCase(5, 5.4, "5,5.4")] + [TestCase(0.5, 0.6, "0.5,0.6")] + public void Can_Parse_Valid_Value_From_Editor(decimal from, decimal to, string expectedResult) { var value = JsonNode.Parse($"{{\"from\": {from}, \"to\": {to}}}"); var fromEditor = FromEditor(value) as string; Assert.AreEqual(expectedResult, fromEditor); } - [Test] - public void Can_Handle_Invalid_Values_To_Editor() + [TestCaseSource(nameof(InvalidCaseData))] + public void Can_Handle_Invalid_Values_To_Editor(object value) { - foreach (var value in _invalidValues) - { - var toEditor = ToEditor(value); - Assert.IsNull(toEditor, message: $"Failed for: {value}"); - } + var toEditor = ToEditor(value); + Assert.IsNull(toEditor, message: $"Failed for: {value}"); } [Test] From 570005f5e1b4f7ce2d9b5c79787e0796d2c71c4b Mon Sep 17 00:00:00 2001 From: Mole Date: Wed, 20 Nov 2024 08:38:33 +0100 Subject: [PATCH 85/95] Run both cms and package migrations in upgrader (#17575) * Run both cms and package migrations in upgrader * Use correct setting --- .../Install/UnattendedUpgrader.cs | 139 ++++++++++++------ .../Runtime/RuntimeState.cs | 9 +- 2 files changed, 100 insertions(+), 48 deletions(-) diff --git a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs index 8ad2b07b23..9bf5c91eb8 100644 --- a/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs +++ b/src/Umbraco.Infrastructure/Install/UnattendedUpgrader.cs @@ -1,5 +1,9 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Exceptions; using Umbraco.Cms.Core.Logging; @@ -23,19 +27,39 @@ public class UnattendedUpgrader : INotificationAsyncHandler unattendedSettings) { _profilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); _umbracoVersion = umbracoVersion ?? throw new ArgumentNullException(nameof(umbracoVersion)); _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); _runtimeState = runtimeState ?? throw new ArgumentNullException(nameof(runtimeState)); _packageMigrationRunner = packageMigrationRunner; + _unattendedSettings = unattendedSettings.Value; + } + + [Obsolete("Use constructor that takes IOptions, this will be removed in V16")] + public UnattendedUpgrader( + IProfilingLogger profilingLogger, + IUmbracoVersion umbracoVersion, + DatabaseBuilder databaseBuilder, + IRuntimeState runtimeState, + PackageMigrationRunner packageMigrationRunner) + : this( + profilingLogger, + umbracoVersion, + databaseBuilder, + runtimeState, + packageMigrationRunner, + StaticServiceProvider.Instance.GetRequiredService>()) + { } public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, CancellationToken cancellationToken) @@ -46,55 +70,26 @@ public class UnattendedUpgrader : INotificationAsyncHandler( - "Starting unattended upgrade.", - "Unattended upgrade completed.")) - { - DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); - if (result?.Success == false) - { - var innerException = new UnattendedInstallException( - "An error occurred while running the unattended upgrade.\n" + result.Message); - _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException); - } + RunUpgrade(notification); - notification.UnattendedUpgradeResult = - RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete; + // If we errored out when upgrading don't do anything. + if (notification.UnattendedUpgradeResult is RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors) + { + return Task.CompletedTask; + } + + // It's entirely possible that there's both a core upgrade and package migrations to run, so try and run package migrations too. + // but only if upgrade unattended is enabled. + if (_unattendedSettings.PackageMigrationsUnattended) + { + RunPackageMigrations(notification); } } break; case RuntimeLevelReason.UpgradePackageMigrations: { - if (!_runtimeState.StartupState.TryGetValue( - RuntimeState.PendingPackageMigrationsStateKey, - out var pm) - || pm is not IReadOnlyList pendingMigrations) - { - throw new InvalidOperationException( - $"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state"); - } - - if (pendingMigrations.Count == 0) - { - throw new InvalidOperationException( - "No pending migrations found but the runtime level reason is " + - RuntimeLevelReason.UpgradePackageMigrations); - } - - try - { - _packageMigrationRunner.RunPackagePlans(pendingMigrations); - notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult - .PackageMigrationComplete; - } - catch (Exception ex) - { - SetRuntimeError(ex); - notification.UnattendedUpgradeResult = - RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors; - } + RunPackageMigrations(notification); } break; @@ -106,6 +101,64 @@ public class UnattendedUpgrader : INotificationAsyncHandler pendingMigrations) + { + throw new InvalidOperationException( + $"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state"); + } + + if (pendingMigrations.Count == 0) + { + // If we determined we needed to run package migrations but there are none, this is an error + if (_runtimeState.Reason is RuntimeLevelReason.UpgradePackageMigrations) + { + throw new InvalidOperationException( + "No pending migrations found but the runtime level reason is " + + RuntimeLevelReason.UpgradePackageMigrations); + } + + return; + } + + try + { + _packageMigrationRunner.RunPackagePlans(pendingMigrations); + notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult + .PackageMigrationComplete; + } + catch (Exception ex) + { + SetRuntimeError(ex); + notification.UnattendedUpgradeResult = + RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors; + } + } + + private void RunUpgrade(RuntimeUnattendedUpgradeNotification notification) + { + var plan = new UmbracoPlan(_umbracoVersion); + using (!_profilingLogger.IsEnabled(Core.Logging.LogLevel.Verbose) ? null : _profilingLogger.TraceDuration( + "Starting unattended upgrade.", + "Unattended upgrade completed.")) + { + DatabaseBuilder.Result? result = _databaseBuilder.UpgradeSchemaAndData(plan); + if (result?.Success == false) + { + var innerException = new UnattendedInstallException( + "An error occurred while running the unattended upgrade.\n" + result.Message); + _runtimeState.Configure(RuntimeLevel.BootFailed, RuntimeLevelReason.BootFailedOnException, innerException); + } + + notification.UnattendedUpgradeResult = + RuntimeUnattendedUpgradeNotification.UpgradeResult.CoreUpgradeComplete; + } + } + private void SetRuntimeError(Exception exception) => _runtimeState.Configure( RuntimeLevel.BootFailed, diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 53eecfc8d3..e353fa55b9 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -325,18 +325,17 @@ public class RuntimeState : IRuntimeState // All will be prefixed with the same key. IReadOnlyDictionary? keyValues = database.GetFromKeyValueTable(Constants.Conventions.Migrations.KeyValuePrefix); - // This could need both an upgrade AND package migrations to execute but - // we will process them one at a time, first the upgrade, then the package migrations. + // This could need both an upgrade AND package migrations to execute, so always add any pending package migrations + IReadOnlyList packagesRequiringMigration = _packageMigrationState.GetPendingPackageMigrations(keyValues); + _startupState[PendingPackageMigrationsStateKey] = packagesRequiringMigration; + if (DoesUmbracoRequireUpgrade(keyValues)) { return UmbracoDatabaseState.NeedsUpgrade; } - IReadOnlyList packagesRequiringMigration = _packageMigrationState.GetPendingPackageMigrations(keyValues); if (packagesRequiringMigration.Count > 0) { - _startupState[PendingPackageMigrationsStateKey] = packagesRequiringMigration; - return UmbracoDatabaseState.NeedsPackageMigration; } } From ec8e10f406a6d9f993aa371f772ffa565f61e232 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 21 Nov 2024 08:53:59 +0100 Subject: [PATCH 86/95] Fix RTE console error when blocks are not available (#17582) --- .../src/views/propertyeditors/rte/rte.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js index f2e6d78a77..4c552b19b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.component.js @@ -349,8 +349,8 @@ if(modelObject) { modelObject.update(vm.model.value.blocks, $scope); vm.tinyMceEditor.fire('updateBlocks'); + onLoaded(); } - onLoaded(); } function ensurePropertyValue(newVal) { From 9febbc7db189d297a2b6a2fc6cb695982c908ea7 Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Thu, 21 Nov 2024 09:07:22 +0100 Subject: [PATCH 87/95] Distinguish between default zero and intentional zero sort order for new documents (#17517) * Distinguish between default value and initial zero * Update special value comment documentation * Redid solution with dirty/new entity tracking * rework copy branch sortorder fix * Change == false to is false --------- Co-authored-by: Mole --- src/Umbraco.Core/Services/ContentService.cs | 3 +++ .../Persistence/Repositories/Implement/DocumentRepository.cs | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 8bdaba271e..59656de6b5 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2760,6 +2760,9 @@ public class ContentService : RepositoryService, IContentService 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/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 3951ffba69..90c12c994a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -900,9 +900,10 @@ public class DocumentRepository : ContentRepositoryBase Date: Thu, 21 Nov 2024 16:19:48 +0100 Subject: [PATCH 88/95] Sort manifest file paths alphabetically (#14466) * Sort manifest file paths alphabetically * Update src/Umbraco.Infrastructure/Manifest/ManifestParser.cs Co-authored-by: Ronald Barendse --------- Co-authored-by: Ronald Barendse --- src/Umbraco.Infrastructure/Manifest/ManifestParser.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs index 4dbd6abd40..f43f6852a6 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs @@ -250,6 +250,11 @@ public class ManifestParser : IManifestParser return Array.Empty(); } - return Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + var files = Directory.GetFiles(_path, "package.manifest", SearchOption.AllDirectories); + + // Ensure a consistent, alphabetical sorting of paths, because this is not guaranteed to be the same between file systems or OSes + Array.Sort(files); + + return files; } } From cbd4dc6e0dca40d5acde47fc43b280a3c1e3a9c5 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 22 Nov 2024 09:06:10 +0100 Subject: [PATCH 89/95] Handle "all slashes" routes (#17596) --- src/Umbraco.Core/Routing/UriUtility.cs | 6 ++++++ .../Umbraco.Core/Routing/UriUtilityTests.cs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs index fb59ada249..1869641fb5 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -111,6 +111,12 @@ public sealed class UriUtility if (path != "/") { path = path.TrimEnd(Constants.CharArrays.ForwardSlash); + + // perform fallback to root if the path was all slashes (i.e. https://some.where//////) + if (path == string.Empty) + { + path = "/"; + } } return uri.Rewrite(path); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs index 906ee0e808..6f0c00f8db 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Routing/UriUtilityTests.cs @@ -23,7 +23,9 @@ public class UriUtilityTests // test that the trailing slash goes but not on hostname [TestCase("http://LocalHost/", "http://localhost/")] + [TestCase("http://LocalHost/////", "http://localhost/")] [TestCase("http://LocalHost/Home/", "http://localhost/home")] + [TestCase("http://LocalHost/Home/////", "http://localhost/home")] [TestCase("http://LocalHost/Home/?x=y", "http://localhost/home?x=y")] [TestCase("http://LocalHost/Home/Sub1/", "http://localhost/home/sub1")] [TestCase("http://LocalHost/Home/Sub1/?x=y", "http://localhost/home/sub1?x=y")] From 772c523a2365947b80a049769b042310a7c18e1b Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:55:40 +0100 Subject: [PATCH 90/95] Validate email for member models (#17532) * Validate email for member models * Add null check or more test cases * return invalid when not a valid email * Cleanup * remove private method in favor of extension * Remove non used, using statement --------- Co-authored-by: Elitsa (cherry picked from commit 6b0f8e7b7c1cd6f09cf8768b7d97adaa32d61881) --- src/Umbraco.Core/Extensions/StringExtensions.cs | 9 +++++++++ src/Umbraco.Core/Services/UserService.cs | 6 ++---- .../Services/MemberEditingService.cs | 5 +++++ .../ShortStringHelper/StringExtensionsTests.cs | 10 ++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 7c144138b0..e5eb0819e9 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Security.Cryptography; @@ -1558,6 +1559,14 @@ public static class StringExtensions yield return sb.ToString(); } + /// + /// Checks whether a string is a valid email address. + /// + /// The string check + /// Returns a bool indicating whether the string is an email address. + public static bool IsEmail(this string? email) => + string.IsNullOrWhiteSpace(email) is false && new EmailAddressAttribute().IsValid(email); + // having benchmarked various solutions (incl. for/foreach, split and LINQ based ones), // this is by far the fastest way to find string needles in a string haystack public static int CountOccurrences(this string haystack, string needle) diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 0167dc6d92..9b57e5b94b 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -915,7 +915,7 @@ internal class UserService : RepositoryService, IUserService { return UserOperationStatus.UserNameIsNotEmail; } - if (!IsEmailValid(model.Email)) + if (model.Email.IsEmail() is false) { return UserOperationStatus.InvalidEmail; } @@ -1134,7 +1134,7 @@ internal class UserService : RepositoryService, IUserService return UserOperationStatus.UserNameIsNotEmail; } - if (IsEmailValid(model.Email) is false) + if (model.Email.IsEmail() is false) { return UserOperationStatus.InvalidEmail; } @@ -1162,8 +1162,6 @@ internal class UserService : RepositoryService, IUserService return UserOperationStatus.Success; } - private static bool IsEmailValid(string email) => new EmailAddressAttribute().IsValid(email); - private List? GetIdsFromKeys(IEnumerable? guids, UmbracoObjectTypes type) { var keys = guids? diff --git a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs index f9f155523a..061f3ecf62 100644 --- a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs +++ b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs @@ -225,6 +225,11 @@ internal sealed class MemberEditingService : IMemberEditingService return MemberEditingOperationStatus.InvalidUsername; } + if (model.Email.IsEmail() is false) + { + return MemberEditingOperationStatus.InvalidEmail; + } + if (password is not null) { IdentityResult validatePassword = await _memberManager.ValidatePasswordAsync(password); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs index 58e06ba17b..9043a1a854 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs @@ -364,6 +364,16 @@ public class StringExtensionsTests TryIsFullPath(" ", false, false); // technically, a valid filename on Linux } + [TestCase("test@test.com", true)] + [TestCase("test@test", true)] + [TestCase("testtest.com", false)] + [TestCase("test@test.dk", true)] + [TestCase("test@test.se", true)] + [TestCase(null, false)] + [TestCase("", false)] + [TestCase(" ", false)] + public void IsEmail(string? email, bool isEmail) => Assert.AreEqual(isEmail, email.IsEmail()); + private static void TryIsFullPath(string path, bool expectedIsFull, bool expectedIsValid = true) { Assert.AreEqual(expectedIsFull, path.IsFullPath(), "IsFullPath('" + path + "')"); From ba4120050f5d280095fc1bc6ec0c47779ca29415 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:27:22 +0100 Subject: [PATCH 91/95] Add cache key to GetByUserName (#17350) * Add cache key to GetByUserName * Remove constants * create new cache policy for member repository --------- Co-authored-by: Elitsa --- .../MemberRepositoryUsernameCachePolicy.cs | 33 +++++++++++++++++++ .../Implement/MemberRepository.cs | 6 ++-- 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs diff --git a/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs new file mode 100644 index 0000000000..1dc5f42a01 --- /dev/null +++ b/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs @@ -0,0 +1,33 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Core.Cache; + +public class MemberRepositoryUsernameCachePolicy : DefaultRepositoryCachePolicy +{ + public MemberRepositoryUsernameCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) : base(cache, scopeAccessor, options) + { + } + + public IMember? GetByUserName(string key, string? username, Func performGetByUsername, Func?> performGetAll) + { + var cacheKey = GetEntityCacheKey(key + username); + IMember? fromCache = Cache.GetCacheItem(cacheKey); + + // if found in cache then return else fetch and cache + if (fromCache != null) + { + return fromCache; + } + + IMember? entity = performGetByUsername(username); + + if (entity != null && entity.HasIdentity) + { + InsertEntity(cacheKey, entity); + } + + return entity; + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index c89344716f..053cb15b2d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; public class MemberRepository : ContentRepositoryBase, IMemberRepository { private readonly IJsonSerializer _jsonSerializer; - private readonly IRepositoryCachePolicy _memberByUsernameCachePolicy; + private readonly MemberRepositoryUsernameCachePolicy _memberByUsernameCachePolicy; private readonly IMemberGroupRepository _memberGroupRepository; private readonly IMemberTypeRepository _memberTypeRepository; private readonly MemberPasswordConfigurationSettings _passwordConfiguration; @@ -67,7 +67,7 @@ public class MemberRepository : ContentRepositoryBase(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + new MemberRepositoryUsernameCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } /// @@ -228,7 +228,7 @@ public class MemberRepository : ContentRepositoryBase - _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); + _memberByUsernameCachePolicy.GetByUserName("uRepo_userNameKey+", username, PerformGetByUsername, PerformGetAllByUsername); public int[] GetMemberIds(string[] usernames) { From 9141f61708a54d3e1e08803de491c3126d042753 Mon Sep 17 00:00:00 2001 From: Lee Kelleher Date: Mon, 25 Nov 2024 14:51:16 +0000 Subject: [PATCH 92/95] V13: Dropzone, upload complete callback with processed file array (#17631) * Dropzone, upload complete callback with processed file array * Media card: cosmetic fix for image border-radius The image's square corners were poking out. --- .../directives/components/upload/umbfiledropzone.directive.js | 2 +- .../src/less/components/umb-media-grid.less | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 8c51763364..395ea161b4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -116,7 +116,7 @@ angular.module("umbraco.directives") if (scope.totalMessages === 0) { if (scope.filesUploaded) { //queue is empty, trigger the done action - scope.filesUploaded(scope.done); + scope.filesUploaded(scope.processed); } //auto-clear the done queue after 3 secs diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index d25fe62c08..a2f72087c0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -29,7 +29,7 @@ > div { overflow: hidden; - border-radius: @baseBorderRadius; + border-radius: 0 0 @baseBorderRadius @baseBorderRadius; } } @@ -106,6 +106,7 @@ position: relative; object-fit: contain; height: 100%; + border-radius: @baseBorderRadius; } .umb-media-grid__item-image-placeholder { From 7c617f29766f31254687a913278142b06ea85ee4 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 26 Nov 2024 11:06:40 +0100 Subject: [PATCH 93/95] Revert #14234 add update error message for DB connection failures (#17612) * Revert #14234 * Make the boot failure message more descriptive when unable to connect to DB * Update src/Umbraco.Infrastructure/Runtime/RuntimeState.cs Co-authored-by: Ronald Barendse * Revert changes * Obsolete InstallMissingDatabase from V16 --------- Co-authored-by: Ronald Barendse --- src/Umbraco.Core/Configuration/Models/GlobalSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs index d3135cc373..3edecc1c16 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -129,6 +129,7 @@ public class GlobalSettings /// /// Gets or sets a value indicating whether to install the database when it is missing. /// + [Obsolete("This option will be removed in V16.")] [DefaultValue(StaticInstallMissingDatabase)] public bool InstallMissingDatabase { get; set; } = StaticInstallMissingDatabase; From 4590739fa5c751a7acd7cdd8e874a38d1e1cc695 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 26 Nov 2024 11:26:44 +0100 Subject: [PATCH 94/95] Add ASCII file name conversion (#17580) --- .../Models/RequestHandlerSettings.cs | 17 +++++++++++++++++ .../Strings/DefaultShortStringHelperConfig.cs | 15 ++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs index 672577b1b7..a1d8f95a37 100644 --- a/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/RequestHandlerSettings.cs @@ -15,6 +15,7 @@ public class RequestHandlerSettings { internal const bool StaticAddTrailingSlash = true; internal const string StaticConvertUrlsToAscii = "try"; + internal const string StaticConvertFileNamesToAscii = "false"; internal const bool StaticEnableDefaultCharReplacements = true; internal static readonly CharItem[] DefaultCharCollection = @@ -73,6 +74,22 @@ public class RequestHandlerSettings /// public bool ShouldTryConvertUrlsToAscii => ConvertUrlsToAscii.InvariantEquals("try"); + /// + /// Gets or sets a value indicating whether to convert file names to ASCII (valid values: "true", "try" or "false"). + /// + [DefaultValue(StaticConvertFileNamesToAscii)] + public string ConvertFileNamesToAscii { get; set; } = StaticConvertFileNamesToAscii; + + /// + /// Gets a value indicating whether URLs should be converted to ASCII. + /// + public bool ShouldConvertFileNamesToAscii => ConvertFileNamesToAscii.InvariantEquals("true"); + + /// + /// Gets a value indicating whether URLs should be tried to be converted to ASCII. + /// + public bool ShouldTryConvertFileNamesToAscii => ConvertFileNamesToAscii.InvariantEquals("try"); + /// /// Disable all default character replacements /// diff --git a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs index ec7ed9d002..7a4a968351 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -74,12 +74,21 @@ public class DefaultShortStringHelperConfig { urlSegmentConvertTo = CleanStringType.Ascii; } - - if (requestHandlerSettings.ShouldTryConvertUrlsToAscii) + else if (requestHandlerSettings.ShouldTryConvertUrlsToAscii) { urlSegmentConvertTo = CleanStringType.TryAscii; } + CleanStringType fileNameSegmentConvertTo = CleanStringType.Utf8; + if (requestHandlerSettings.ShouldConvertFileNamesToAscii) + { + fileNameSegmentConvertTo = CleanStringType.Ascii; + } + else if (requestHandlerSettings.ShouldTryConvertFileNamesToAscii) + { + fileNameSegmentConvertTo = CleanStringType.TryAscii; + } + return WithConfig(CleanStringType.UrlSegment, new Config { PreFilter = ApplyUrlReplaceCharacters, @@ -92,7 +101,7 @@ public class DefaultShortStringHelperConfig { PreFilter = ApplyUrlReplaceCharacters, IsTerm = (c, leading) => char.IsLetterOrDigit(c) || c == '_', // letter, digit or underscore - StringType = CleanStringType.Utf8 | CleanStringType.LowerCase, + StringType = fileNameSegmentConvertTo | CleanStringType.LowerCase, BreakTermsOnUpper = false, Separator = '-', }).WithConfig(CleanStringType.Alias, new Config From 28756d449b9bd74728790b8b0481b2cfc048ee9e Mon Sep 17 00:00:00 2001 From: Sven Geusens Date: Wed, 27 Nov 2024 14:35:22 +0100 Subject: [PATCH 95/95] Rectify v13 in v14 merge --- Directory.Packages.props | 3 --- src/Umbraco.Core/Services/ContentService.cs | 2 -- .../Persistence/Repositories/Implement/AuditRepository.cs | 6 +++--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 3ddec5d013..027876da6f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -89,8 +89,5 @@ - - - diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index aebdc92620..651745cf40 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2484,8 +2484,6 @@ public class ContentService : RepositoryService, IContentService scope.Complete(); return OperationResult.Succeed(eventMessages); } - - return OperationResult.Succeed(eventMessages); } // MUST be called from within WriteLock diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs index 7bb5690135..d7dc4f8161 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -104,7 +104,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe totalRecords = page.TotalItems; var items = page.Items.Select( - dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Datestamp, dto.Comment, dto.Parameters)).ToList(); + dto => new AuditItem(dto.NodeId, Enum.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp)).ToList(); // map the DateStamp for (var i = 0; i < items.Count; i++) @@ -149,7 +149,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe LogDto? dto = Database.First(sql); return dto == null ? null - : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Datestamp, dto.Comment, dto.Parameters); + : new AuditItem(dto.NodeId, Enum.Parse(dto.Header), dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters, dto.Datestamp); } protected override IEnumerable PerformGetAll(params int[]? ids) => throw new NotImplementedException(); @@ -162,7 +162,7 @@ internal class AuditRepository : EntityRepositoryBase, IAuditRe List? dtos = Database.Fetch(sql); - return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Datestamp, x.Comment, x.Parameters)).ToList(); + return dtos.Select(x => new AuditItem(x.NodeId, Enum.Parse(x.Header), x.UserId ?? Constants.Security.UnknownUserId, x.EntityType, x.Comment, x.Parameters, x.Datestamp)).ToList(); } protected override Sql GetBaseQuery(bool isCount)