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 000000000000..ff99115ae5ae --- /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 a5527095d79a..76bb769c9e89 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 e723bfcdd284..8bdaba271ecf 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2448,22 +2448,26 @@ public OperationResult MoveToRecycleBin(IContent content, int userId = Constants /// 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 void Move(IContent content, int parentId, int userId = Constants.Security 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 void Move(IContent content, int parentId, int userId = Constants.Security 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 0d3cc80b8208..1733a741425e 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 81b37bb10819..62cbae301270 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -2254,7 +2254,12 @@ public async Task PostSort(ContentSortOrder sorted) 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 216b7fe1e860..0a1aee1f277c 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 8a8ae0a36b5d..e3b3ba413c51 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 f003e1afa633..e76da32a545b 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 18707c1f60d5..7ed6b9ee500b 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 0a688083eb19..47a787aada14 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 1634f60baa36..51012200bd05 100644 --- a/src/Umbraco.Core/Services/LocalizedTextService.cs +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -350,7 +350,13 @@ private IDictionary> GetAreaStoredTranslatio 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 @@ private IDictionary> GetAreaStoredTranslatio } } - overallResult.Add(area.Attribute("alias")!.Value, result); + if (!overallResult.ContainsKey(areaAlias)) + { + overallResult.Add(areaAlias, result); + } } // Merge English Dictionary @@ -374,11 +383,11 @@ private IDictionary> GetAreaStoredTranslatio 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 @@ private IDictionary> GetAreaStoredTranslatio } } - 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 0a688083eb19..47a787aada14 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 1634f60baa36..51012200bd05 100644 --- a/src/Umbraco.Core/Services/LocalizedTextService.cs +++ b/src/Umbraco.Core/Services/LocalizedTextService.cs @@ -350,7 +350,13 @@ private IDictionary> GetAreaStoredTranslatio 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 @@ private IDictionary> GetAreaStoredTranslatio } } - overallResult.Add(area.Attribute("alias")!.Value, result); + if (!overallResult.ContainsKey(areaAlias)) + { + overallResult.Add(areaAlias, result); + } } // Merge English Dictionary @@ -374,11 +383,11 @@ private IDictionary> GetAreaStoredTranslatio 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 @@ private IDictionary> GetAreaStoredTranslatio } } - 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 ff1a0edbe511..cb406a4d2210 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 d8fbca45d48e..c46bb890b147 100644 --- a/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs +++ b/src/Umbraco.Web.Website/Routing/UmbracoRouteValuesFactory.cs @@ -128,11 +128,12 @@ private async Task CheckNoTemplateAsync( 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 9a1ecead89b2..1ef672270ef8 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 void Configure(ImageSharpMiddlewareOptions options) return Task.CompletedTask; } - int width = context.Parser.ParseValue(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 361891de6a13..679e105b72d5 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 67bdaf7395ce..461ed59c8eee 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 @@ protected override void Migrate() // If the new column already exists we'll do nothing. if (ColumnExists(Constants.DatabaseSchema.Tables.UserGroup, NewColumnName)) { + Context.Complete(); return; } @@ -31,10 +32,12 @@ protected override void Migrate() 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 a5500fd8de2f..b2c598f6ef0b 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 bb227d8a121a..99eeb24f0a52 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 30706ad5de01..a6949b54fa36 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 000000000000..f02822053331 --- /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 000000000000..1c9fbc542f21 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowVaryByCulture.spec.ts @@ -0,0 +1,128 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const secondLanguageName = 'Danish'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.language.ensureNameNotExists(secondLanguageName); + await umbracoApi.language.createDanishLanguage(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.language.ensureNameNotExists(secondLanguageName); +}); + +test('can create content with allow vary by culture enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.documentType.createDocumentTypeWithAllowVaryByCulture(documentTypeName); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); +}); + +test('can create content with names that vary by culture', async ({umbracoApi, umbracoUi}) => { + // Arrange + const danishContentName = 'Test indhold'; + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowVaryByCulture(documentTypeName); + await umbracoApi.document.createDefaultDocumentWithEnglishCulture(contentName, documentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickVariantSelectorButton(); + await umbracoUi.content.clickVariantAddModeButton(); + await umbracoUi.content.enterContentName(danishContentName); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(danishContentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(danishContentName); + expect(contentData.variants.length).toBe(2); + expect(contentData.variants[0].name).toBe(contentName); + expect(contentData.variants[1].name).toBe(danishContentName); +}); + +test('can create content with names that vary by culture and content that is invariant', async ({umbracoApi, umbracoUi}) => { + // Arrange + const danishContentName = 'Test indhold'; + const textContent = 'This is a test text'; + const danishTextContent = 'Dette er testtekst'; + const dataTypeName = 'Textstring'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', false); + await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, textContent, dataTypeName); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickVariantSelectorButton(); + await umbracoUi.content.clickVariantAddModeButton(); + await umbracoUi.content.enterContentName(danishContentName); + await umbracoUi.content.enterTextstring(danishTextContent); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(danishContentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(danishContentName); + expect(contentData.variants.length).toBe(2); + expect(contentData.variants[0].name).toBe(contentName); + expect(contentData.variants[1].name).toBe(danishContentName); + expect(contentData.values.length).toBe(1); + expect(contentData.values[0].value).toBe(danishTextContent); +}); + +test('can create content with names and content that vary by culture', async ({umbracoApi, umbracoUi}) => { + // Arrange + const danishContentName = 'Test indhold'; + const textContent = 'This is a test text'; + const danishTextContent = 'Dette er testtekst'; + const dataTypeName = 'Textstring'; + const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeData.id, 'Test Group', true); + await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, textContent, dataTypeName, true); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.goToContentWithName(contentName); + await umbracoUi.content.clickVariantSelectorButton(); + await umbracoUi.content.clickVariantAddModeButton(); + await umbracoUi.content.enterContentName(danishContentName); + await umbracoUi.content.enterTextstring(danishTextContent); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(danishContentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(danishContentName); + expect(contentData.variants.length).toBe(2); + expect(contentData.variants[0].name).toBe(contentName); + expect(contentData.variants[1].name).toBe(danishContentName); + expect(contentData.values.length).toBe(2); + expect(contentData.values[0].value).toBe(textContent); + expect(contentData.values[1].value).toBe(danishTextContent); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts new file mode 100644 index 000000000000..2016a0ee4c27 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedChildNodes.spec.ts @@ -0,0 +1,98 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; + +test.beforeEach(async ({umbracoApi, umbracoUi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoUi.goToBackOffice(); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with allowed child node enabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const childDocumentTypeName = 'Test Child Document Type'; + const childDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(childDocumentTypeName); + await umbracoApi.documentType.createDocumentTypeWithAllowedChildNode(documentTypeName, childDocumentTypeId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + + // Clean + await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName); +}); + +test('cannot create child content if allowed child node is disabled', async ({umbracoApi, umbracoUi}) => { + // Arrange + const noAllowedDocumentTypeAvailableMessage = 'There are no allowed Document Types available for creating content here'; + const documentTypeId = await umbracoApi.documentType.createDefaultDocumentTypeWithAllowAsRoot(documentTypeName); + await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickCreateButton(); + + // Assert + await umbracoUi.content.isDocumentTypeNameVisible(documentTypeName, false); + await umbracoUi.content.doesModalHaveText(noAllowedDocumentTypeAvailableMessage); +}); + +test('can create multiple child nodes with different document types', async ({umbracoApi, umbracoUi}) => { + // Arrange + const firstChildDocumentTypeName = 'First Child Document Type'; + const secondChildDocumentTypeName = 'Second Child Document Type'; + const firstChildContentName = 'First Child Content'; + const secondChildContentName = 'Second Child Content'; + const firstChildDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(firstChildDocumentTypeName); + const secondChildDocumentTypeId = await umbracoApi.documentType.createDefaultDocumentType(secondChildDocumentTypeName); + const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedTwoChildNodes(documentTypeName, firstChildDocumentTypeId, secondChildDocumentTypeId); + const contentId = await umbracoApi.document.createDefaultDocument(contentName, documentTypeId); + await umbracoApi.document.createDefaultDocumentWithParent(firstChildContentName, firstChildDocumentTypeId, contentId); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(secondChildDocumentTypeName); + // This wait is needed + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.enterContentName(secondChildContentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(secondChildContentName)).toBeTruthy(); + const childData = await umbracoApi.document.getChildren(contentId); + expect(childData.length).toBe(2); + expect(childData[0].variants[0].name).toBe(firstChildContentName); + expect(childData[1].variants[0].name).toBe(secondChildContentName); + // verify that the child content displays in the tree after reloading children + await umbracoUi.content.clickActionsMenuForContent(contentName); + await umbracoUi.content.clickReloadButton(); + await umbracoUi.content.clickCaretButtonForContentName(contentName); + await umbracoUi.content.doesContentTreeHaveName(firstChildContentName); + await umbracoUi.content.doesContentTreeHaveName(secondChildContentName); + + // Clean + await umbracoApi.document.ensureNameNotExists(firstChildContentName); + await umbracoApi.document.ensureNameNotExists(secondChildContentName); + await umbracoApi.documentType.ensureNameNotExists(firstChildDocumentTypeName); + await umbracoApi.documentType.ensureNameNotExists(secondChildDocumentTypeName); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts new file mode 100644 index 000000000000..f58d86c36e9a --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithAllowedTemplates.spec.ts @@ -0,0 +1,66 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const contentName = 'TestContent'; +const documentTypeName = 'TestDocumentTypeForContent'; +const templateName = 'TestTemplate'; +let templateId = ''; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.template.ensureNameNotExists(templateName); + templateId = await umbracoApi.template.createDefaultTemplate(templateName); +}); + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); +}); + +test('can create content with an allowed template', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.documentType.createDocumentTypeWithAllowedTemplate(documentTypeName, templateId, true); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.template.id).toBe(templateId); +}); + +test('can create content with multiple allowed templates', async ({umbracoApi, umbracoUi}) => { + // Arrange + const defaultTemplateName = 'TestDefaultTemplate'; + await umbracoApi.template.ensureNameNotExists(defaultTemplateName); + const defaultTemplateId = await umbracoApi.template.createDefaultTemplate(templateName); + await umbracoApi.documentType.createDocumentTypeWithTwoAllowedTemplates(documentTypeName, templateId, defaultTemplateId, true, defaultTemplateId); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(contentName); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.isSuccessNotificationVisible(); + expect(await umbracoApi.document.doesNameExist(contentName)).toBeTruthy(); + const contentData = await umbracoApi.document.getByName(contentName); + expect(contentData.template.id).toBe(defaultTemplateId); + + // Clean + await umbracoApi.template.ensureNameNotExists(defaultTemplateName); +}); \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/ContentWithDocumentTypeProperties/ContentWithCollections.spec.ts new file mode 100644 index 000000000000..837f7ababa9a --- /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 eb87b7288a79..3f19d68085a0 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 9a1ecead89b2..1ef672270ef8 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 void Configure(ImageSharpMiddlewareOptions options) return Task.CompletedTask; } - int width = context.Parser.ParseValue(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 9a1ecead89b2..79fcd0a9bf05 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 void Configure(ImageSharpMiddlewareOptions options) return Task.CompletedTask; } - int width = context.Parser.ParseValue(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 1ef672270ef8..79fcd0a9bf05 100644 --- a/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs +++ b/src/Umbraco.Cms.Imaging.ImageSharp/ConfigureImageSharpMiddlewareOptions.cs @@ -51,8 +51,11 @@ public void Configure(ImageSharpMiddlewareOptions options) if (context.Commands.Contains(ResizeWebProcessor.Width)) { - if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), NumberStyles.Integer, - CultureInfo.InvariantCulture, out var width) + if (!int.TryParse( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Width), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var width) || width < 0 || width >= _imagingSettings.Resize.MaxWidth) { @@ -62,8 +65,11 @@ public void Configure(ImageSharpMiddlewareOptions options) if (context.Commands.Contains(ResizeWebProcessor.Height)) { - if (!int.TryParse(context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), NumberStyles.Integer, - CultureInfo.InvariantCulture, out var height) + if (!int.TryParse( + context.Commands.GetValueOrDefault(ResizeWebProcessor.Height), + NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var height) || height < 0 || height >= _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 8daa1b689b01..dcc67bf5d32e 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 void Configure(ImageSharpMiddlewareOptions options) return Task.CompletedTask; } - var width = context.Parser.ParseValue( - 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 9e7207bf071a..faa31ae51103 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 000000000000..0a39d0c4f959 --- /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 8dcf57923aa3..803eb63c79d5 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 b2c598f6ef0b..71eeca096330 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 c513c83d70b3..705292c82a6f 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.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 ecb8d7d9f7b7..a45302464ad3 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.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 c5469d575f63..8f22721b5e6c 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.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 98c3868c65fd..d32de4bf7ac1 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 3b2f02bc6665..05117000e84a 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 f49d971518b0..6bec70b51b3b 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 71eeca096330..224d17f3d43c 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 71eeca096330..224d17f3d43c 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 224d17f3d43c..586bde9f2316 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 224d17f3d43c..586bde9f2316 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 cb406a4d2210..ba35bbaff3c8 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 b70970673a31..f31b9fca0608 100644 --- a/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs +++ b/src/Umbraco.Core/Routing/UmbracoRequestPaths.cs @@ -148,7 +148,10 @@ private static bool IsPluginControllerRoute(string path) /// /// 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 d4856c2b1213..9c0e4cbb16f0 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 void Is_Back_Office_Request(string input, string virtualPath, bool expect [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 | 82 ++++++++++--------- .../PublishedCache/ContentCacheTests.cs | 77 +++++++++++++++++ 4 files changed, 135 insertions(+), 50 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 45dbdbbd109a..d8885ed25693 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 0230032dc292..d706301ff848 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 override void Release(bool completed) private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -327,14 +327,16 @@ private void EnsureLocked() 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 @@ private void Lock(WriteLockInfo lockInfo, bool forceGen = false) // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -374,6 +377,7 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -409,7 +413,7 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) { if (lockInfo.Taken) { - Monitor.Exit(_wlocko); + _writeLock.Release(); } } } @@ -1817,7 +1821,7 @@ public Snapshot CreateSnapshot() // 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 Snapshot CreateSnapshot() } 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 b6c87e22bb09..70bdcfe03860 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 ScopedWriteLock(SnapDictionary dictionary, bool scoped) private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -195,14 +191,16 @@ private void EnsureLocked() 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 @@ private void Lock(WriteLockInfo lockInfo, bool forceGen = false) // RuntimeHelpers.PrepareConstrainedRegions(); try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -244,43 +243,48 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) return; } - if (commit == false) + try { - lock (_rlocko) + if (commit == false) { - try - { - } - finally + lock (_rlocko) { - // forget about the temp. liveGen - _nextGen = false; - _liveGen -= 1; + try + { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution + } + finally + { + // forget about the temp. liveGen + _nextGen = false; + _liveGen -= 1; + } } - } - foreach (KeyValuePair> item in _items) - { - LinkedNode? link = item.Value; - if (link.Gen <= _liveGen) + foreach (KeyValuePair> item in _items) { - continue; - } + 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); + 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 Snapshot CreateSnapshot() // 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 @@ internal class TestHelper 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 000000000000..6507bd6cdacc --- /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 | 82 ++++++++++--------- .../PublishedCache/ContentCacheTests.cs | 77 +++++++++++++++++ 4 files changed, 135 insertions(+), 50 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 45dbdbbd109a..d8885ed25693 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 0230032dc292..d706301ff848 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 override void Release(bool completed) private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -327,14 +327,16 @@ private void EnsureLocked() 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 @@ private void Lock(WriteLockInfo lockInfo, bool forceGen = false) // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -374,6 +377,7 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -409,7 +413,7 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) { if (lockInfo.Taken) { - Monitor.Exit(_wlocko); + _writeLock.Release(); } } } @@ -1817,7 +1821,7 @@ public Snapshot CreateSnapshot() // 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 Snapshot CreateSnapshot() } 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 b6c87e22bb09..70bdcfe03860 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 ScopedWriteLock(SnapDictionary dictionary, bool scoped) private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -195,14 +191,16 @@ private void EnsureLocked() 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 @@ private void Lock(WriteLockInfo lockInfo, bool forceGen = false) // RuntimeHelpers.PrepareConstrainedRegions(); try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -244,43 +243,48 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) return; } - if (commit == false) + try { - lock (_rlocko) + if (commit == false) { - try - { - } - finally + lock (_rlocko) { - // forget about the temp. liveGen - _nextGen = false; - _liveGen -= 1; + try + { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution + } + finally + { + // forget about the temp. liveGen + _nextGen = false; + _liveGen -= 1; + } } - } - foreach (KeyValuePair> item in _items) - { - LinkedNode? link = item.Value; - if (link.Gen <= _liveGen) + foreach (KeyValuePair> item in _items) { - continue; - } + 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); + 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 Snapshot CreateSnapshot() // 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 @@ internal class TestHelper 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 000000000000..6507bd6cdacc --- /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 99eeb24f0a52..4ce3aee211b1 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 a6949b54fa36..f41b929af2b5 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 f02822053331..595a4aa3e2d9 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 369bbb769284..73ed97bd6159 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 2b5ce0857d8a..400027741c19 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 59a68325cfc7..ab3438654027 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 1390bbdc4f65..7308be62d802 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 9b1ad8f8be3f..6f095bc27565 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 46fdf12ff53e..adebbd26f7d4 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 87c69bb6a972..4f9c65b0e762 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 000000000000..9a394ffd07cb --- /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 0c7a869eb039..693762a0ec3a 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 cf3b771973b6..12fddb5d0ccb 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataType.spec.ts @@ -1,4 +1,4 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const dataTypeName = 'TestDataType'; @@ -24,7 +24,7 @@ test('can create a data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); }); @@ -41,6 +41,7 @@ test('can rename a data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.dataType.clickSaveButton(); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); expect(await umbracoApi.dataType.doesNameExist(wrongDataTypeName)).toBeFalsy(); }); @@ -55,7 +56,7 @@ test('can delete a data type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.dataType.deleteDataType(dataTypeName); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeFalsy(); }); @@ -75,6 +76,7 @@ test('can change property editor in a data type', {tag: '@smoke'}, async ({umbra await umbracoUi.dataType.clickSaveButton(); // Assert + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.dataType.doesNameExist(dataTypeName)).toBeTruthy(); const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); expect(dataTypeData.editorAlias).toBe(updatedEditorAlias); @@ -110,7 +112,7 @@ test('can change settings', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.dataType.clickSaveButton(); // Assert - await umbracoUi.dataType.isSuccessNotificationVisible(); + await umbracoUi.dataType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName); expect(dataTypeData.values).toContainEqual(expectedDataTypeValues); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/DataType/DataTypeFolder.spec.ts index 400027741c19..e2177f63371e 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 68ad9ee39b81..00b31a883829 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 803eb63c79d5..ea45f76b42c7 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 80ce20aff81a..43c10cd3fabb 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 f44a164a2066..ebecb2dd2a29 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 db71845ef117..67e5844fee18 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentBlueprint/DocumentBlueprint.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const documentBlueprintName = 'TestDocumentBlueprints'; @@ -29,7 +29,7 @@ test('can create a document blueprint from the settings menu', {tag: '@smoke'}, await umbracoUi.documentBlueprint.clickSaveButton(); // Assert - await umbracoUi.documentBlueprint.isSuccessNotificationVisible(); + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeTruthy(); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, true); }); @@ -48,6 +48,7 @@ test('can rename a document blueprint', async ({umbracoApi, umbracoUi}) => { await umbracoUi.documentBlueprint.clickSaveButton(); // Assert + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeTruthy(); expect(await umbracoApi.documentBlueprint.doesNameExist(wrongDocumentBlueprintName)).toBeFalsy(); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, true, false); @@ -67,7 +68,7 @@ test('can delete a document blueprint', async ({umbracoApi, umbracoUi}) => { await umbracoUi.documentBlueprint.clickConfirmToDeleteButton(); // Assert - await umbracoUi.documentBlueprint.isSuccessNotificationVisible(); + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeFalsy(); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, false, false); }); @@ -85,7 +86,7 @@ test('can create a document blueprint from the content menu', async ({umbracoApi await umbracoUi.content.clickSaveModalButton(); // Assert - await umbracoUi.content.isSuccessNotificationVisible(); + await umbracoUi.documentBlueprint.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.documentBlueprint.doesNameExist(documentBlueprintName)).toBeTruthy(); await umbracoUi.documentBlueprint.goToSettingsTreeItem('Document Blueprints'); await umbracoUi.documentBlueprint.isDocumentBlueprintRootTreeItemVisible(documentBlueprintName, true); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentType.spec.ts index e12749b1cd43..20f3f096ec83 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 a96999f4418c..320c7f60bf3d 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 9d756daab3bf..fb1e9c7a7a2c 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Language/Language.spec.ts @@ -1,4 +1,4 @@ -import {test} from '@umbraco/playwright-testhelpers'; +import {NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from "@playwright/test"; const languageName = 'Arabic'; @@ -25,7 +25,7 @@ test('can add language', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => { await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.language.doesExist(isoCode)).toBeTruthy(); // Verify the created language displays in the list await umbracoUi.language.clickLanguagesMenu(); @@ -44,7 +44,7 @@ test('can update default language option', {tag: '@smoke'}, async ({umbracoApi, await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.isDefault).toBe(true); @@ -67,7 +67,7 @@ test('can update mandatory language option', async ({umbracoApi, umbracoUi}) => await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.isMandatory).toBe(true); }); @@ -82,7 +82,7 @@ test('can delete language', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => await umbracoUi.language.removeLanguageByName(languageName); // Assert - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.language.doesExist(isoCode)).toBeFalsy(); await umbracoUi.language.isLanguageNameVisible(languageName, false); }); @@ -99,7 +99,7 @@ test('can remove fallback language', async ({umbracoApi, umbracoUi}) => { await umbracoUi.language.clickSaveButton(); // Act - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.fallbackIsoCode).toBeFalsy(); }); @@ -117,7 +117,7 @@ test('can add fallback language', async ({umbracoApi, umbracoUi}) => { await umbracoUi.language.clickSaveButton(); // Act - await umbracoUi.language.isSuccessNotificationVisible(); + await umbracoUi.language.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const languageData = await umbracoApi.language.get(isoCode); expect(languageData.fallbackIsoCode).toBe(defaultLanguageIsoCode); }); @@ -134,5 +134,5 @@ test('cannot add a language with duplicate ISO code', async ({umbracoApi, umbrac await umbracoUi.language.clickSaveButton(); // Assert - await umbracoUi.language.isErrorNotificationVisible(); + await umbracoUi.language.doesErrorNotificationHaveText(NotificationConstantHelper.error.duplicateISOcode); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts index 96d562206eba..0690dac4ef08 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaType.spec.ts @@ -1,5 +1,5 @@ import {expect} from "@playwright/test"; -import {AliasHelper, ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; const mediaTypeName = 'TestMediaType'; @@ -22,7 +22,7 @@ test('can create a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy(); }); @@ -38,7 +38,7 @@ test('can rename a media type', async ({umbracoApi, umbracoUi}) => { await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeTruthy(); }); @@ -56,7 +56,7 @@ test('can update the alias for a media type', async ({umbracoApi, umbracoUi}) => await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); expect(mediaTypeData.alias).toBe(updatedAlias); }); @@ -72,7 +72,7 @@ test('can add an icon for a media type', {tag: '@smoke'}, async ({umbracoApi, um await umbracoUi.mediaType.clickSaveButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const mediaTypeData = await umbracoApi.mediaType.getByName(mediaTypeName); expect(mediaTypeData.icon).toBe(bugIcon); await umbracoUi.mediaType.isTreeItemVisible(mediaTypeName, true); @@ -89,6 +89,6 @@ test('can delete a media type', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.mediaType.clickConfirmToDeleteButton(); // Assert - await umbracoUi.mediaType.isSuccessNotificationVisible(); + await umbracoUi.mediaType.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.mediaType.doesNameExist(mediaTypeName)).toBeFalsy(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/MediaType/MediaTypeFolder.spec.ts index 83006c9e54db..7863d90fee79 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 6f095bc27565..bd81a05d72e1 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 58741a3d7a57..cae5b85dc22b 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 282a503021c1..3c3f009838ff 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 d8f105867d99..e7453a2a4cbf 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 43ee3af2d75c..bd6de2872563 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/Stylesheet.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/Stylesheet.spec.ts @@ -1,4 +1,4 @@ -import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; import {expect} from '@playwright/test'; const stylesheetName = 'TestStyleSheetFile.css'; @@ -27,7 +27,7 @@ test('can create a empty stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbra await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName); }); @@ -46,7 +46,7 @@ test('can create a stylesheet with content', async ({umbracoApi, umbracoUi}) => await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(stylesheetContent); @@ -67,7 +67,7 @@ test.skip('can create a new Rich Text Editor stylesheet file', {tag: '@smoke'}, await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); expect(await umbracoApi.stylesheet.doesExist(stylesheetName)).toBeTruthy(); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(stylesheetContent); @@ -87,7 +87,7 @@ test.skip('can update a stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbrac await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(stylesheetContent); }); @@ -103,7 +103,7 @@ test('can delete a stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.stylesheet.clickDeleteAndConfirmButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.deleted); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeFalsy(); await umbracoUi.stylesheet.isStylesheetRootTreeItemVisible(stylesheetName, false, false); }); @@ -121,7 +121,7 @@ test('can rename a stylesheet', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) await umbracoUi.stylesheet.rename(stylesheetName); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.renamed); expect(await umbracoApi.stylesheet.doesNameExist(stylesheetName)).toBeTruthy(); expect(await umbracoApi.stylesheet.doesNameExist(wrongStylesheetName)).toBeFalsy(); }); @@ -143,7 +143,7 @@ test('can edit rich text editor styles', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(newStylesheetContent); }); @@ -161,7 +161,7 @@ test('can remove rich text editor styles', async ({umbracoApi, umbracoUi}) => { await umbracoUi.stylesheet.clickSaveButton(); // Assert - await umbracoUi.stylesheet.isSuccessNotificationVisible(); + await umbracoUi.stylesheet.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); const stylesheetData = await umbracoApi.stylesheet.getByName(stylesheetName); expect(stylesheetData.content).toEqual(''); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/Stylesheet/StylesheetFolder.spec.ts index edc2552fbaa5..42bd719e56ae 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 adebbd26f7d4..64eb299a9450 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 4f9c65b0e762..ec223fa5c819 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 47df4cf5101c..1096af77d670 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 43435394c1ee..fb5de6c69872 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 f479e709011e..51caf6043f6d 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 4500c2812b74..ed8a10f42ff6 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 4d6bf155d351..0ea425c69ae7 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 47df4cf5101c..1096af77d670 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 | 82 ++++++++++--------- .../PublishedCache/ContentCacheTests.cs | 77 +++++++++++++++++ 4 files changed, 135 insertions(+), 50 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 45dbdbbd109a..d8885ed25693 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 0230032dc292..d706301ff848 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 override void Release(bool completed) private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -327,14 +327,16 @@ private void EnsureLocked() 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 @@ private void Lock(WriteLockInfo lockInfo, bool forceGen = false) // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -374,6 +377,7 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) // see SnapDictionary try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -409,7 +413,7 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) { if (lockInfo.Taken) { - Monitor.Exit(_wlocko); + _writeLock.Release(); } } } @@ -1817,7 +1821,7 @@ public Snapshot CreateSnapshot() // 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 Snapshot CreateSnapshot() } 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 b6c87e22bb09..70bdcfe03860 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 ScopedWriteLock(SnapDictionary dictionary, bool scoped) private void EnsureLocked() { - if (!Monitor.IsEntered(_wlocko)) + if (_writeLock.CurrentCount != 0) { throw new InvalidOperationException("Write lock must be acquried."); } @@ -195,14 +191,16 @@ private void EnsureLocked() 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 @@ private void Lock(WriteLockInfo lockInfo, bool forceGen = false) // RuntimeHelpers.PrepareConstrainedRegions(); try { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution } finally { @@ -244,43 +243,48 @@ private void Release(WriteLockInfo lockInfo, bool commit = true) return; } - if (commit == false) + try { - lock (_rlocko) + if (commit == false) { - try - { - } - finally + lock (_rlocko) { - // forget about the temp. liveGen - _nextGen = false; - _liveGen -= 1; + try + { + // Run all code in finally to ensure ThreadAbortException does not interrupt execution + } + finally + { + // forget about the temp. liveGen + _nextGen = false; + _liveGen -= 1; + } } - } - foreach (KeyValuePair> item in _items) - { - LinkedNode? link = item.Value; - if (link.Gen <= _liveGen) + foreach (KeyValuePair> item in _items) { - continue; - } + 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); + 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 Snapshot CreateSnapshot() // 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 @@ internal class TestHelper 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 000000000000..6507bd6cdacc --- /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 72693f113d23..f3a15e71542e 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml +++ b/tests/Umbraco.Tests.AcceptanceTest/fixtures/packageLibrary/package.xml @@ -11,8 +11,6 @@ - - diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/CreatedPackages.spec.ts index 0b72f77f0ccd..244aaa3f4bdd 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 603b52d1e71b..9164b496f5d9 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 ab3438654027..6d3c159e9599 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 000000000000..24b50a707e3f --- /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 000000000000..f9dec390200b --- /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 000000000000..5d94160d4a53 --- /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 ec223fa5c819..61977241e7e4 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 e3b3ba413c51..20f8c0b91ac4 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 c79506fb5f3f..9dc302c2187e 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 string EnsureInternalLinks(string text) { if (tagData.Udi is not null) { - var newLink = "#"; - if (tagData.Udi?.EntityType == Constants.UdiEntityType.Document) - { - newLink = _publishedUrlProvider.GetUrl(tagData.Udi.Guid); - } - else if (tagData.Udi?.EntityType == Constants.UdiEntityType.Media) + var newLink = tagData.Udi?.EntityType switch { - 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 string EnsureInternalLinks(string text) } // 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 @@ private IEnumerable FindLocalLinkIds(string text) 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 @@ private IEnumerable FindLegacyLocalLinkIds(string text) 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 LocalLinkTag(int? intId, GuidUdi? udi, string tagHref) 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 6ef0f74e2f91..43e4eccedb29 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 void Returns_Udis_From_Legacy_And_Current_LocalLinks() [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 void Returns_Udis_From_Legacy_And_Current_LocalLinks() [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 void Returns_Udis_From_Legacy_And_Current_LocalLinks() "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 9dc302c2187e..b8d6b3f3c528 100644 --- a/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs +++ b/src/Umbraco.Core/Templates/HtmlLocalLinkParser.cs @@ -92,7 +92,7 @@ public string EnsureInternalLinks(string text) { 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 | 984 +++++++++--------- src/Umbraco.Web.UI.Client/package.json | 4 +- src/Umbraco.Web.UI.Login/package-lock.json | 956 ++++++++--------- 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, 1030 insertions(+), 1077 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 4d87f71a6065..18c7fcd688d6 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==", - "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" + "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.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 1c244f489914..ae65e8a6b884 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 b4524339e1e8..0e32d8cc1453 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==", - "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" + "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.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 90e3bf68cb4c..486e3e94eb95 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 8e5b5efd53bf..f63dfa92e60d 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 a22e5e840eb2..3f5bfd1428d9 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 d3e62621728f..000000000000 --- 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 196db95d2664..cf231e1acc85 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 7e95f9a4427e..765843201bca 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 44766fb2dc3f..c5c28ffcfa2c 100644 --- a/src/Umbraco.Core/DynamicRoot/Origin/RootDynamicRootOriginFinder.cs +++ b/src/Umbraco.Core/DynamicRoot/Origin/RootDynamicRootOriginFinder.cs @@ -27,7 +27,9 @@ public RootDynamicRootOriginFinder(IEntityService entityService) 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 d1e515de5921..f9b207db03f8 100644 --- a/src/Umbraco.Core/DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs +++ b/src/Umbraco.Core/DynamicRoot/Origin/SiteDynamicRootOriginFinder.cs @@ -20,12 +20,14 @@ public SiteDynamicRootOriginFinder(IEntityService entityService, IDomainService 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 a6fc601d8fd8..c5b476d6c5fb 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,70 +222,74 @@ 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 () { if($scope.invalidStartNode) { @@ -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 dc5ef96bde56..2ec84196b002 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 0c1042c3ffe4..862bbb9abc93 100644 --- a/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs +++ b/src/Umbraco.PublishedCache.NuCache/DependencyInjection/UmbracoBuilderExtensions.cs @@ -37,23 +37,7 @@ public static IUmbracoBuilder AddNuCache(this IUmbracoBuilder builder) 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 73cb42383723..ca488f8cc203 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -305,20 +305,20 @@ public override IEnumerable GetAllVersions(int nodeId) public IMedia? GetMediaByPath(string mediaPath) { - var umbracoFileValue = mediaPath; const string pattern = ".*[_][0-9]+[x][0-9]+[.].*"; var isResized = Regex.IsMatch(mediaPath, pattern); + var originalMediaPath = string.Empty; // If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" URL. if (isResized) { var underscoreIndex = mediaPath.LastIndexOf('_'); var dotIndex = mediaPath.LastIndexOf('.'); - umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); + originalMediaPath = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex)); } Sql 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 4d6bf155d351..643c331edb64 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 43435394c1ee..b70236ab8068 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 1096af77d670..027876da6fcf 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 0ea425c69ae7..97fd24618cdc 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 fb5de6c69872..656325376c56 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 1096af77d670..027876da6fcf 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 ee9aa0864f37..943141878abc 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 943141878abc..bb56b7cc203f 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 226755039e8a..2a6bbc99d4db 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -59,6 +59,14 @@ public static async Task AuthenticateBackOfficeAsync(this Ht 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 2a65c67a8d34..32abdc2a4854 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 e98a597e764b..734dd29cba30 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 989f8ef09325..0918f6dc5b74 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 36eb3958e213..d1f32fb6b578 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.getThumbnail = function(source) { - return mediaHelper.getThumbnailFromPath(source) || source; - } - vm.getClientSideUrl = function(sourceData) { - return URL.createObjectURL(sourceData); - } + 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 71141106bf30..9fbee2fa9288 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 9fbee2fa9288..41965d5b3e88 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 0f2da0ac4e50..0a84f318f6f1 100644 --- a/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/HttpContextExtensions.cs @@ -59,6 +59,14 @@ public static async Task AuthenticateBackOfficeAsync(this Ht 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 30bb2b6f3f50..e752044ddc2f 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 14de3bb1c4a7..b9e6560f81bf 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 989f8ef09325..0918f6dc5b74 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 36eb3958e213..d1f32fb6b578 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.getThumbnail = function(source) { - return mediaHelper.getThumbnailFromPath(source) || source; - } - vm.getClientSideUrl = function(sourceData) { - return URL.createObjectURL(sourceData); - } + 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 4ce3aee211b1..759237a0742f 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 f41b929af2b5..951329447d13 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 000000000000..63ae1dfd85ce --- /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 000000000000..d541317e5322 --- /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 000000000000..08f7f9bf4c8c --- /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 000000000000..ab2c86f90184 --- /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 9a394ffd07cb..1ccdaec91ede 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 000000000000..207da6bff4fb --- /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 faa31ae51103..2d6606c2c3da 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 6a069ef38cb5..38bd28885990 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 951329447d13..5170f824e10a 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 64dfb64dca11..bb1016b502a7 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 61ae46a2b322..752f18fd0db5 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 be90d8695bbf..7e1d1f163f8f 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 @@ string c(string x) } 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 759237a0742f..def3e8bdb554 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 5170f824e10a..f25f0abc716b 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 63ae1dfd85ce..ba7fa533850b 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 000000000000..7f901fcd87f2 --- /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 000000000000..52e49aeb31c0 --- /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 d541317e5322..8a60b518f47e 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 08f7f9bf4c8c..f1cd346b6606 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 ab2c86f90184..e4f43e51e434 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 1ccdaec91ede..b0fd459563ce 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 207da6bff4fb..9c4164f3213d 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 def3e8bdb554..ce584dba6fd7 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 f25f0abc716b..85ef6be6d729 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 ba7fa533850b..7e73452b4e38 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 7f901fcd87f2..cc48bf55c30a 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 000000000000..73381004f366 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithDropdown.spec.ts @@ -0,0 +1,44 @@ +import {AliasHelper, test} from '@umbraco/playwright-testhelpers'; + +const contentName = 'Test Rendering Content'; +const documentTypeName = 'TestDocumentTypeForContent'; +const customDataTypeName = 'Custom Dropdown'; +const templateName = 'TestTemplateForContent'; +const propertyName = 'Test Dropdown'; + +test.afterEach(async ({umbracoApi}) => { + await umbracoApi.document.ensureNameNotExists(contentName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.template.ensureNameNotExists(templateName); + await umbracoApi.dataType.ensureNameNotExists(customDataTypeName); +}); + +const dropdownValues = [ + {type: 'an empty dropdown list', value: [], isMultiple: false}, + {type: 'a single dropdown value', value: ['Test checkbox'], isMultiple: false}, + {type: 'multiple dropdown values', value: ['Test option 1', 'Test option 2'], isMultiple: true} +]; + +for (const dropdown of dropdownValues) { + test(`can render content with ${dropdown.type}`, async ({umbracoApi, umbracoUi}) => { + // Arrange + const dataTypeId = await umbracoApi.dataType.createDropdownDataType(customDataTypeName, dropdown.isMultiple, dropdown.value); + let templateId = ''; + if (dropdown.isMultiple) { + templateId = await umbracoApi.template.createTemplateWithDisplayingMulitpleStringValue(templateName, AliasHelper.toAlias(propertyName)); + } else { + templateId = await umbracoApi.template.createTemplateWithDisplayingStringValue(templateName, AliasHelper.toAlias(propertyName)); + } + await umbracoApi.document.createPublishedDocumentWithValue(contentName, dropdown.value, dataTypeId, templateId, propertyName, documentTypeName); + const contentData = await umbracoApi.document.getByName(contentName); + const contentURL = contentData.urls[0].url; + + // Act + await umbracoUi.contentRender.navigateToRenderedContentPage(contentURL); + + // Assert + dropdown.value.forEach(async value => { + await umbracoUi.contentRender.doesContentRenderValueContainText(value); + }); + }); +} \ No newline at end of file diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithImageCropper.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/RenderingContent/RenderingContentWithImageCropper.spec.ts new file mode 100644 index 000000000000..b428f16530de --- /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 000000000000..280a8ad72745 --- /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 f1cd346b6606..6fe6e5b33311 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 000000000000..228275de0872 --- /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 000000000000..37fef60b9f4a --- /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 ea45f76b42c7..3e25adf75479 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 24b50a707e3f..e4ab49947991 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 f9dec390200b..573750f08b78 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 5d94160d4a53..ceac7890f78b 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 000000000000..df20ec0397d9 --- /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 000000000000..7b535fb067bc --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Languages.spec.ts @@ -0,0 +1,135 @@ +import {AliasHelper, ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +const documentTypeName = 'TestDocumentType'; +const documentName = 'TestDocument'; +const englishDocumentName = 'EnglishDocument'; +const danishDocumentName = 'DanishDocument'; +const vietnameseDocumentName = 'VietnameseDocument'; +let documentTypeId = null; + +const dataTypeName = 'Textstring'; +let dataTypeId = null; + +const englishIsoCode = 'en-US'; +const danishIsoCode = 'da'; +const vietnameseIsoCode = 'vi'; +const englishLanguageName = 'English (United States)'; +const danishLanguageName = 'Danish'; +const vietnameseLanguageName = 'Vietnamese'; +const cultureVariants = [ + { + isoCode: englishIsoCode, + name: englishDocumentName, + value: 'EnglishValue', + }, + { + isoCode: danishIsoCode, + name: danishDocumentName, + value: 'DanishValue', + }, + { + isoCode: vietnameseIsoCode, + name: vietnameseDocumentName, + value: 'VietnameseValue', + } +]; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); + await umbracoApi.language.ensureIsoCodeNotExists(danishIsoCode); + await umbracoApi.language.ensureIsoCodeNotExists(vietnameseIsoCode); + await umbracoApi.language.createDanishLanguage(); + await umbracoApi.language.createVietnameseLanguage(); + const dataType = await umbracoApi.dataType.getByName(dataTypeName); + dataTypeId = dataType.id; + documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, dataTypeName, dataTypeId, 'TestGroup', true); + await umbracoApi.document.createDocumentWithMultipleVariants(documentName, documentTypeId, AliasHelper.toAlias(dataTypeName), cultureVariants); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); + await umbracoApi.language.ensureIsoCodeNotExists(danishIsoCode); + await umbracoApi.language.ensureIsoCodeNotExists(vietnameseIsoCode); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can rename content with language set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + const updatedContentName = 'UpdatedContentName'; + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], false, [], false, englishIsoCode); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.goToContentWithName(englishDocumentName); + + // Act + await umbracoUi.content.isDocumentReadOnly(false); + await umbracoUi.content.enterContentName(updatedContentName); + await umbracoUi.content.clickSaveButton(); + await umbracoUi.content.clickSaveAndCloseButton(); + + // Assert + await umbracoUi.userGroup.doesSuccessNotificationHaveText(NotificationConstantHelper.success.saved); + await umbracoUi.content.isContentInTreeVisible(updatedContentName); +}); + +test('can not rename content with language not set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId, [], false, [], false, englishIsoCode); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.doesDocumentSectionHaveLanguageSelected(englishLanguageName); + await umbracoUi.content.changeDocumentSectionLanguage(danishLanguageName); + + // Act + await umbracoUi.content.goToContentWithName(danishDocumentName); + + // Assert + await umbracoUi.content.isDocumentReadOnly(); + await umbracoUi.content.isDocumentNameInputEditable(false); +}); + +test('can update content property with language set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.doesDocumentSectionHaveLanguageSelected(englishLanguageName); + + // Act + await umbracoUi.content.goToContentWithName(englishDocumentName); + + // Assert + await umbracoUi.content.isDocumentPropertyEditable(dataTypeName, true); +}); + +test('can not update content property with language not set in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createUserGroupWithLanguageAndContentSection(userGroupName, englishIsoCode); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.userGroup.goToSection(ConstantHelper.sections.content, false); + await umbracoUi.content.doesDocumentSectionHaveLanguageSelected(englishLanguageName); + await umbracoUi.content.changeDocumentSectionLanguage(vietnameseLanguageName); + + // Act + await umbracoUi.content.goToContentWithName(vietnameseDocumentName); + + // Assert + await umbracoUi.content.isDocumentPropertyEditable(dataTypeName, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts new file mode 100644 index 000000000000..0932ab7f622b --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/Sections.spec.ts @@ -0,0 +1,51 @@ +import {ConstantHelper, test} from '@umbraco/playwright-testhelpers'; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +const userGroupName = 'TestUserGroup'; +let userGroupId = null; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.userGroup.ensureNameNotExists(userGroupName); +}); + +test('can go to section defined in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + + // Act + await umbracoUi.goToBackOffice(); + + // Assert + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.content); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); +}); + +test('can not see section that is not defined in userGroup', async ({umbracoApi, umbracoUi}) => { + // Arrange + userGroupId = await umbracoApi.userGroup.createSimpleUserGroupWithContentSection(userGroupName); + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + + // Act + await umbracoUi.goToBackOffice(); + + // Assert + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.content); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.media, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.settings, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.users, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.members, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.dictionary, false); + await umbracoUi.content.isSectionWithNameVisible(ConstantHelper.sections.packages, false); +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/User.spec.ts index 61977241e7e4..427693c8a8dc 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 000000000000..dd5676cab033 --- /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 ce584dba6fd7..75883bf48c0b 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 85ef6be6d729..1696dafbee01 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 000000000000..0c87cb99110d --- /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 bbfca724aa30..7b4eff86e649 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 f11e17a236ed..09fa3d1029ee 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -29,7 +29,7 @@ public IEnumerable Get(AuditType type, IQuery query) 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 @@ public IEnumerable GetPagedResultsByQuery( 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 @@ protected override void PersistUpdatedItem(IAuditItem entity) => 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 @@ protected override IEnumerable PerformGetByQuery(IQuery 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 @@ protected override Sql GetBaseQuery(bool isCount) 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 57f484adf20d..8e7eed1f28cf 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 void Can_Add_Audit_Entry() } } + [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 5471af2f1db8..165533f28706 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.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 51c6ebb3cc5c..3dcdb92bbd91 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 64806179188d..e1ce77717de9 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.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 f36b7074242d..9828cefa10f9 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 7c54170b89d7..377fef87d1a2 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.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 3a2c860ade0e..1cc8a95197d8 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -91,6 +91,7 @@ void AddAllowedApplicationsPolicy(string policyName, params string[] allowedClai 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 9b54a3912ac9..fd811161c7c5 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 75883bf48c0b..a0d2fbdc7429 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 1696dafbee01..237bf0f3dd2f 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 000000000000..eb5ee1f20427 --- /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 693762a0ec3a..07f16cab9fb6 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 573750f08b78..cf545b025bc9 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 000000000000..900081db82bf --- /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 000000000000..61a99709e3f2 --- /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 dd5676cab033..502b40eb8fb3 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 000000000000..df9655388a71 --- /dev/null +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Users/Permissions/UserGroup/ContentWithRichTextEditor.spec.ts @@ -0,0 +1,52 @@ +import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers'; +import {expect} from "@playwright/test"; + +const testUser = ConstantHelper.testUserCredentials; +let testUserCookieAndToken = {cookie: "", accessToken: "", refreshToken: ""}; + +let userGroupId = null; + +const documentTypeName = 'TestDocumentType'; +const documentName = 'TestDocument'; +const richTextEditorName = 'TestRichTextEditor'; +const stylesheetName = 'TestStylesheet.css'; + +test.beforeEach(async ({umbracoApi}) => { + await umbracoApi.user.ensureNameNotExists(testUser.name); + await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); + const stylesheetPath = await umbracoApi.stylesheet.createStylesheetWithHeaderContent(stylesheetName); + const dataTypeId = await umbracoApi.dataType.createRichTextEditorDataTypeWithStylesheet(richTextEditorName, stylesheetPath); + await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, richTextEditorName, dataTypeId); + const userGroup = await umbracoApi.userGroup.getByName('Editors'); + userGroupId = userGroup.id; +}); + +test.afterEach(async ({umbracoApi}) => { + // Ensure we are logged in to admin + await umbracoApi.loginToAdminUser(testUserCookieAndToken.cookie, testUserCookieAndToken.accessToken, testUserCookieAndToken.refreshToken); + await umbracoApi.stylesheet.ensureNameNotExists(stylesheetName); + await umbracoApi.dataType.ensureNameNotExists(richTextEditorName); + await umbracoApi.documentType.ensureNameNotExists(documentTypeName); +}); + +test('can create content with a rich text editor that has a stylesheet', async ({umbracoApi, umbracoUi}) => { + // Arrange + await umbracoApi.user.setUserPermissions(testUser.name, testUser.email, testUser.password, userGroupId); + testUserCookieAndToken = await umbracoApi.user.loginToUser(testUser.name, testUser.email, testUser.password); + await umbracoUi.goToBackOffice(); + await umbracoUi.content.goToSection(ConstantHelper.sections.content, false); + + // Act + await umbracoUi.content.clickActionsMenuAtRoot(); + await umbracoUi.content.clickCreateButton(); + await umbracoUi.content.chooseDocumentType(documentTypeName); + await umbracoUi.content.enterContentName(documentName); + // Is needed to make sure that the rich text editor is loaded + await umbracoUi.waitForTimeout(500); + await umbracoUi.content.doesErrorNotificationHaveText(NotificationConstantHelper.error.noAccessToResource, false); + await umbracoUi.content.clickSaveButton(); + + // Assert + await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.created); + expect(await umbracoApi.document.doesNameExist(documentName)).toBeTruthy(); +}); 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 277ef4c4f304..a43e662b76c9 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 55df93ef6303..c20037319a8a 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.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 141dbd80ee9e..2dacf50fef45 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 @@ 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 b19bfcd3ce86..e0d24d0973c7 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 2439118ac712..8ab82ecb07ae 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.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 bb3e87e7bcb9..a79bb4c2a3ad 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 73ad7b63748a..7b4395382d7b 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 d2ef0cf0e4d0..ef9e8db235e8 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 2915662da335..f3be972803de 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.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 1cc8a95197d8..c79e64e4f179 100644 --- a/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs +++ b/src/Umbraco.Cms.Api.Management/DependencyInjection/BackOfficeAuthPolicyBuilderExtensions.cs @@ -87,6 +87,7 @@ void AddAllowedApplicationsPolicy(string policyName, params string[] allowedClai 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 fd811161c7c5..b27f4a85b294 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 3fdae52115db..bbf1dec98fcd 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 ByPathPartialViewController( _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 a72c8129b581..b44990b41e2c 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 DeletePartialViewController( _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 df15c726b5e8..727f4d1d68d1 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 ByPathPartialViewFolderController(IPartialViewFolderService partialViewFo _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 121cb1dae7bc..41cf9548d58b 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 a935f87a8065..e460b0adf0ae 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 UpdatePartialViewController( _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 67a3b605a70e..60c5fe644a19 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 ByPathScriptController( _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 74dc1554e814..94c2e0a1c0ed 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 DeleteScriptController( _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 84543bffe475..698c94f685a2 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 ByPathScriptFolderController(IScriptFolderService scriptFolderService, IU _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 b5fd5022b7d6..48d97e9b4b97 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 87ff3502bafd..a18f82ebc861 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 UpdateScriptController( _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 a7e3a6139f36..87aec60133e7 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 ByPathStylesheetController( _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 3dcdb92bbd91..9996ddeae0e1 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 DeleteStylesheetController( _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 d5d0cca84f0f..95c7d853abf4 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 ByPathStylesheetFolderController(IStylesheetFolderService stylesheetFolde _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 38760b34e701..deb38f086594 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 377fef87d1a2..7000ca7b402d 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 UpdateStylesheetController( _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 752f18fd0db5..24c1ac4d1c9b 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 c0b1d0710713..021c938d028e 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 244aaa3f4bdd..0d9a1f3e2efe 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 9164b496f5d9..fbd11c8cfd24 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Packages/InstalledPackages.spec.ts @@ -1,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 6d3c159e9599..eb33d7b20da9 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 d499f47a9cc2..f2e6d78a771d 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 832191567710..b67e4af0a2d2 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 SliderPropertyValueEditor( 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 SliderPropertyValueEditor( 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 SliderPropertyValueEditor( 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 0850422598f6..6ec212c02e49 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.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 void Can_Parse_Single_Value_To_Editor(string value, int expected) [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 void Can_Parse_Range_Value_To_Editor(string value, int expectedFrom, int [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 8ad2b07b2310..9bf5c91eb81a 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 Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance { case RuntimeLevelReason.UpgradeMigrations: { - 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; - } - } + RunUpgrade(notification); - break; - case RuntimeLevelReason.UpgradePackageMigrations: - { - if (!_runtimeState.StartupState.TryGetValue( - RuntimeState.PendingPackageMigrationsStateKey, - out var pm) - || pm is not IReadOnlyList pendingMigrations) + // If we errored out when upgrading don't do anything. + if (notification.UnattendedUpgradeResult is RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors) { - throw new InvalidOperationException( - $"The required key {RuntimeState.PendingPackageMigrationsStateKey} does not exist in startup state"); + return Task.CompletedTask; } - if (pendingMigrations.Count == 0) + // 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) { - throw new InvalidOperationException( - "No pending migrations found but the runtime level reason is " + - RuntimeLevelReason.UpgradePackageMigrations); + RunPackageMigrations(notification); } + } - try - { - _packageMigrationRunner.RunPackagePlans(pendingMigrations); - notification.UnattendedUpgradeResult = RuntimeUnattendedUpgradeNotification.UpgradeResult - .PackageMigrationComplete; - } - catch (Exception ex) - { - SetRuntimeError(ex); - notification.UnattendedUpgradeResult = - RuntimeUnattendedUpgradeNotification.UpgradeResult.HasErrors; - } + break; + case RuntimeLevelReason.UpgradePackageMigrations: + { + RunPackageMigrations(notification); } break; @@ -106,6 +101,64 @@ public Task HandleAsync(RuntimeUnattendedUpgradeNotification notification, Cance return Task.CompletedTask; } + private void RunPackageMigrations(RuntimeUnattendedUpgradeNotification notification) + { + if (_runtimeState.StartupState.TryGetValue( + RuntimeState.PendingPackageMigrationsStateKey, + out var pm) is false + || pm is not IReadOnlyList 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 53eecfc8d334..e353fa55b951 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -325,18 +325,17 @@ private UmbracoDatabaseState GetUmbracoDatabaseState(IUmbracoDatabaseFactory dat // 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 f2e6d78a771d..4c552b19b5a4 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 8bdaba271ecf..59656de6b597 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2760,6 +2760,9 @@ public bool RecycleBinSmells() descendantCopy.CreatorId = userId; descendantCopy.WriterId = userId; + // since the repository relies on the dirty state to figure out whether it needs to update the sort order, we mark it dirty here + descendantCopy.SortOrder = descendantCopy.SortOrder; + // save and flush (see above) _documentRepository.Save(descendantCopy); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 3951ffba6928..90c12c994aa0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -900,9 +900,10 @@ protected override void PersistNewItem(IContent entity) NodeDto parent = GetParentNodeDto(entity.ParentId); var level = parent.Level + 1; - var sortOrderExists = SortorderExists(entity.ParentId, entity.SortOrder); + var calculateSortOrder = (entity is { HasIdentity: false, SortOrder: 0 } && entity.IsPropertyDirty(nameof(entity.SortOrder)) is false) // SortOrder was not updated from it's default value + || SortorderExists(entity.ParentId, entity.SortOrder); // if the sortorder of the entity already exists get a new one, else use the sortOrder of the entity - var sortOrder = sortOrderExists ? GetNewChildSortOrder(entity.ParentId, 0) : entity.SortOrder; + var sortOrder = calculateSortOrder ? GetNewChildSortOrder(entity.ParentId, 0) : entity.SortOrder; // persist the node dto NodeDto nodeDto = dto.ContentDto.NodeDto; From c7014e159b93a177b19077cdedcddee93faf180b Mon Sep 17 00:00:00 2001 From: Matt Brailsford 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 4dbd6abd4066..f43f6852a62b 100644 --- a/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs +++ b/src/Umbraco.Infrastructure/Manifest/ManifestParser.cs @@ -250,6 +250,11 @@ private IEnumerable GetManifestFiles() 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 fb59ada249be..1869641fb5aa 100644 --- a/src/Umbraco.Core/Routing/UriUtility.cs +++ b/src/Umbraco.Core/Routing/UriUtility.cs @@ -111,6 +111,12 @@ public Uri UriToUmbraco(Uri uri) 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 906ee0e80834..6f0c00f8db79 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 7c144138b011..e5eb0819e94a 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 IEnumerable EscapedSplit(this string value, char splitChar 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 0167dc6d929f..9b57e5b94b04 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -915,7 +915,7 @@ private async Task ValidateUserCreateModel(UserCreateModel { return UserOperationStatus.UserNameIsNotEmail; } - if (!IsEmailValid(model.Email)) + if (model.Email.IsEmail() is false) { return UserOperationStatus.InvalidEmail; } @@ -1134,7 +1134,7 @@ private UserOperationStatus ValidateUserUpdateModel(IUser existingUser, UserUpda return UserOperationStatus.UserNameIsNotEmail; } - if (IsEmailValid(model.Email) is false) + if (model.Email.IsEmail() is false) { return UserOperationStatus.InvalidEmail; } @@ -1162,8 +1162,6 @@ private UserOperationStatus ValidateUserUpdateModel(IUser existingUser, UserUpda 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 f9f155523a40..061f3ecf6230 100644 --- a/src/Umbraco.Infrastructure/Services/MemberEditingService.cs +++ b/src/Umbraco.Infrastructure/Services/MemberEditingService.cs @@ -225,6 +225,11 @@ private async Task ValidateMemberDataAsync(MemberE 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 58e06ba17b2b..9043a1a854a2 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 void IsFullPath() 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 000000000000..1dc5f42a0117 --- /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 c89344716ff1..053cb15b2d08 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 MemberRepository( _memberGroupRepository = memberGroupRepository; _passwordConfiguration = passwordConfiguration.Value; _memberByUsernameCachePolicy = - new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + new MemberRepositoryUsernameCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } /// @@ -228,7 +228,7 @@ public override IEnumerable GetPage(IQuery? query, } public IMember? GetByUsername(string? username) => - _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 8c51763364ab..395ea161b4ae 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 d25fe62c08ef..a2f72087c03b 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 d3135cc3732a..3edecc1c161b 100644 --- a/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/GlobalSettings.cs @@ -129,6 +129,7 @@ public string UmbracoPath /// /// 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 672577b1b723..a1d8f95a373a 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 ec7ed9d0023c..7a4a96835171 100644 --- a/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs +++ b/src/Umbraco.Core/Strings/DefaultShortStringHelperConfig.cs @@ -74,12 +74,21 @@ public DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings request { 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 DefaultShortStringHelperConfig WithDefault(RequestHandlerSettings request { 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 3ddec5d01337..027876da6fcf 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 aebdc926201b..651745cf4010 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -2484,8 +2484,6 @@ public OperationResult Move(IContent content, int parentId, int userId = Constan 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 7bb56901357a..d7dc4f8161ee 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -104,7 +104,7 @@ public IEnumerable GetPagedResultsByQuery( 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 @@ protected override void PersistUpdatedItem(IAuditItem entity) => 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 @@ protected override IEnumerable PerformGetByQuery(IQuery 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)