From baacd6f27fc9bc276fc374f7efd32122757fe38f Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 24 Feb 2020 15:50:13 +0000 Subject: [PATCH 01/42] Merge pull request #7654 from kjac/v8/fix/recycle-bin-without-mculture V8: Ensure that the recycle bin doesn't delete content at root (cherry picked from commit 4275401c4b3f5695c6fa454a9f428775df0558c1) --- .../src/views/dashboard/dashboard.tabs.controller.js | 4 +++- .../views/propertyeditors/listview/listview.controller.js | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index a35a404c24..cdabb05fd4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -220,7 +220,7 @@ function startupLatestEditsController($scope) { } angular.module("umbraco").controller("Umbraco.Dashboard.StartupLatestEditsController", startupLatestEditsController); -function MediaFolderBrowserDashboardController($rootScope, $scope, $location, contentTypeResource, userService) { +function MediaFolderBrowserDashboardController($scope, $routeParams, $location, contentTypeResource, userService) { var currentUser = {}; @@ -251,6 +251,8 @@ function MediaFolderBrowserDashboardController($rootScope, $scope, $location, co view: dt.view }; + // tell the list view to list content at root + $routeParams.id = -1; }); } else if (currentUser.startMediaIds.length > 0){ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 3c954b9316..175199379a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -276,6 +276,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } $scope.reloadView = function (id, reloadActiveNode) { + if (!id) { + return; + } $scope.viewLoaded = false; $scope.folders = []; @@ -713,10 +716,10 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time } function initView() { - //default to root id if the id is undefined var id = $routeParams.id; if (id === undefined) { - id = -1; + // no ID found in route params - don't list anything as we don't know for sure where we are + return; } getContentTypesCallback(id).then(function (listViewAllowedTypes) { From 78a46677e5b0937a210d786c5703b74a6935d0e2 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 25 Feb 2020 09:43:56 +0000 Subject: [PATCH 02/42] Merge pull request #7630 from kjac/v8/fix/nested-content-single-mode-add-more-than-one V8: Nested Content can't add multiple items (cherry picked from commit a65bf08f1a488a0c38962fe45165c09b50445ea4) --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 7de3a5b567..1c6d305c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -221,6 +221,7 @@ if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) { // only one scaffold type - no need to display the picker addNode(vm.scaffolds[0].contentTypeAlias); + vm.overlayMenu = null; return; } From e2f54c3f3a2a5c290a24de5f4aacd30e78ed1fef Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 25 Feb 2020 13:07:34 +0000 Subject: [PATCH 03/42] Merge pull request #7696 from kjac/v8/fix/login-culture-unset-2 V8: Make sure mculture is set after logout and login (cherry picked from commit 4acddb288a423ecfe09ba44040a2a041f55307c5) --- .../src/navigation.controller.js | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/navigation.controller.js b/src/Umbraco.Web.UI.Client/src/navigation.controller.js index 194c45afe6..281be2d331 100644 --- a/src/Umbraco.Web.UI.Client/src/navigation.controller.js +++ b/src/Umbraco.Web.UI.Client/src/navigation.controller.js @@ -257,6 +257,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar evts.push(eventsService.on("app.ready", function (evt, data) { $scope.authenticated = true; ensureInit(); + ensureMainCulture(); })); // event for infinite editors @@ -279,8 +280,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar } })); - - + /** + * For multi language sites, this ensures that mculture is set to either the last selected language or the default one + */ + function ensureMainCulture() { + if ($location.search().mculture) { + return; + } + var language = lastLanguageOrDefault(); + if (!language) { + return; + } + // trigger a language selection in the next digest cycle + $timeout(function () { + $scope.selectLanguage(language); + }); + } /** * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down @@ -385,28 +400,19 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar if ($scope.languages.length > 1) { //if there's already one set, check if it exists - var currCulture = null; + var language = null; var mainCulture = $location.search().mculture; if (mainCulture) { - currCulture = _.find($scope.languages, function (l) { + language = _.find($scope.languages, function (l) { return l.culture.toLowerCase() === mainCulture.toLowerCase(); }); } - if (!currCulture) { - // no culture in the request, let's look for one in the cookie that's set when changing language - var defaultCulture = $cookies.get("UMB_MCULTURE"); - if (!defaultCulture || !_.find($scope.languages, function (l) { - return l.culture.toLowerCase() === defaultCulture.toLowerCase(); - })) { - // no luck either, look for the default language - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); - if (defaultLang) { - defaultCulture = defaultLang.culture; - } + if (!language) { + language = lastLanguageOrDefault(); + + if (language) { + $location.search("mculture", language.culture); } - $location.search("mculture", defaultCulture ? defaultCulture : null); } } @@ -431,6 +437,25 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar }); }); } + + function lastLanguageOrDefault() { + if (!$scope.languages || $scope.languages.length <= 1) { + return null; + } + // see if we can find a culture in the cookie set when changing language + var lastCulture = $cookies.get("UMB_MCULTURE"); + var language = lastCulture ? _.find($scope.languages, function (l) { + return l.culture.toLowerCase() === lastCulture.toLowerCase(); + }) : null; + if (!language) { + // no luck, look for the default language + language = _.find($scope.languages, function (l) { + return l.isDefault; + }); + } + return language; + } + function nodeExpandedHandler(args) { //store the reference to the expanded node path if (args.node) { From 5a35ba60c1fa3506eb6aa6467de1be846df5b6ca Mon Sep 17 00:00:00 2001 From: ksingercoffey <48767938+ksingercoffey@users.noreply.github.com> Date: Wed, 12 Feb 2020 22:41:19 -0800 Subject: [PATCH 04/42] #7643: add type="button" to prevent default clicks by pressing Enter (#7651) (cherry picked from commit 5307a8cf13975a07f987b93b8a45d73eb88ea5df) --- .../src/views/propertyeditors/mediapicker/mediapicker.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index b17906272d..c4dba4d373 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -36,16 +36,16 @@
- -
  • -
  • From b48e288d717cf51dedfe80623fff27b5e5e36a43 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 14:28:37 +0100 Subject: [PATCH 05/42] Fix JS errors for Nested Content in single mode (cherry picked from commit f4cd1c2f0cdaada66b28c60c9a3dd8742dcd8a7e) --- .../nestedcontent/nestedcontent.controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 1c6d305c6b..19547c38e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -277,6 +277,9 @@ }; vm.getName = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } var name = ""; @@ -326,6 +329,10 @@ }; vm.getIcon = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } + var scaffold = getScaffold(model.value[idx].ncContentTypeAlias); return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } From 05354b2ce9846ec86211534774935706ad34f30b Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Tue, 25 Feb 2020 22:26:18 +0000 Subject: [PATCH 06/42] Merge pull request #7140 from kjac/v8/fix/media-picker-logout-login V8: Make sure the media picker can survive a logout and subsequent login (cherry picked from commit e36df809c05283cc3fdc79cc4ff1f207bfc96742) --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 01f61a16ae..35c68c55bf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -318,7 +318,7 @@ angular.module("umbraco") // also make sure the node is not trashed if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder", path: node.path }); + gotoFolder({ id: $scope.lastOpenedNode || $scope.startNodeId, name: "Media", icon: "icon-folder", path: node.path }); return true; } else { gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); From 2e40b11cd189687697995263f42bd9c8423971c0 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 1 Mar 2020 20:07:49 +0100 Subject: [PATCH 07/42] Make sure entity media path is mapped for DTO types of GenericContentEntityDto (cherry picked from commit fbe963a542b938bc678570372061667caffdc133) --- .../Repositories/Implement/EntityRepository.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 0eafebbfde..24040bf393 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -654,10 +654,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var entity = new MediaEntitySlim(); BuildContentEntity(entity, dto); - if (dto is MediaEntityDto contentDto) + // fill in the media info + if (dto is MediaEntityDto mediaEntityDto) { - // fill in the media info - entity.MediaPath = contentDto.MediaPath; + entity.MediaPath = mediaEntityDto.MediaPath; + } + else if (dto is GenericContentEntityDto genericContentEntityDto) + { + entity.MediaPath = genericContentEntityDto.MediaPath; } return entity; From cbd4746c761108e8db68111f2bb0f5ff8576865d Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 4 Mar 2020 10:04:00 +0100 Subject: [PATCH 08/42] Cherry pick of 7bfeae7329bd4b94baa3cacd5b33a1c8fc33b5c8 Merge pull request #7745 from kjac/v8/fix/media-picker-upload-rte V8: Fix broken "upload and insert media" in RTE --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index 35c68c55bf..ba4c982b5e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -300,9 +300,7 @@ angular.module("umbraco") }); } else { var image = $scope.images[$scope.images.length - 1]; - $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); - selectMedia(image); + clickHandler(image); } }); }); From 69483d03b665520cc344a6dbb443757af9aea691 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 9 Mar 2020 09:57:22 +0000 Subject: [PATCH 09/42] Set version to 8.6.0 .\build SetUmbracoVersion 8.6.0 --- src/SolutionInfo.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 641c06bcfc..1bb53004a0 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.6.0")] -[assembly: AssemblyInformationalVersion("8.6.0-rc")] +[assembly: AssemblyInformationalVersion("8.6.0")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index a2dfa0ffd2..8f0db76c1d 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -429,4 +429,4 @@ - + \ No newline at end of file From 3eb4c642c39709840ce117a00a64c63f2526c7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Mon, 9 Mar 2020 17:07:56 +0100 Subject: [PATCH 10/42] Added GetSegment() method to VariationContext and uses it when contentId is available --- .../PublishedContent/VariationContext.cs | 7 +++++++ .../VariationContextAccessorExtensions.cs | 21 ++++++++++++++++++- .../PublishedValueFallback.cs | 4 ++-- .../PublishedCache/NuCache/Property.cs | 8 +++---- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs index b83002fdce..424fc7c4a8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContext.cs @@ -23,5 +23,12 @@ /// Gets the segment. /// public string Segment { get; } + + /// + /// Gets the segment for the content item + /// + /// + /// + public virtual string GetSegment(int contentId) => Segment; } } diff --git a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs index 6710d79cc6..a387ea87b8 100644 --- a/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs +++ b/src/Umbraco.Core/Models/PublishedContent/VariationContextAccessorExtensions.cs @@ -3,13 +3,32 @@ public static class VariationContextAccessorExtensions { public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment); + + public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string culture, ref string segment) + => variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment); + + private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string culture, ref string segment) { if (culture != null && segment != null) return; // use context values var publishedVariationContext = variationContextAccessor?.VariationContext; if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : ""; - if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : ""; + + if (segment == null) + { + if (variations.VariesBySegment()) + { + segment = contentId == null + ? publishedVariationContext?.Segment + : publishedVariationContext?.GetSegment(contentId.Value); + } + else + { + segment = ""; + } + } } } } diff --git a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs index 7b467b6d15..57c3094eaf 100644 --- a/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs +++ b/src/Umbraco.Web/Models/PublishedContent/PublishedValueFallback.cs @@ -111,7 +111,7 @@ namespace Umbraco.Web.Models.PublishedContent var propertyType = content.ContentType.GetPropertyType(alias); if (propertyType != null) { - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); noValueProperty = content.GetProperty(alias); } @@ -168,7 +168,7 @@ namespace Umbraco.Web.Models.PublishedContent { culture = null; segment = null; - _variationContextAccessor.ContextualizeVariation(propertyType.Variations, ref culture, ref segment); + _variationContextAccessor.ContextualizeVariation(propertyType.Variations, content.Id, ref culture, ref segment); } property = content?.GetProperty(alias); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs index 0254b815c1..86023bb302 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Property.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Property.cs @@ -90,7 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // determines whether a property has value public override bool HasValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); var value = GetSourceValue(culture, segment); var hasValue = PropertyType.IsValue(value, PropertyValueLevel.Source); @@ -194,7 +194,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetSourceValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); if (culture == "" && segment == "") return _sourceValue; @@ -208,7 +208,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); object value; lock (_locko) @@ -229,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.NuCache public override object GetXPathValue(string culture = null, string segment = null) { - _content.VariationContextAccessor.ContextualizeVariation(_variations, ref culture, ref segment); + _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); lock (_locko) { From 5531c7300dbca4ec3253795bfa19786d2f52697a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 19 Mar 2020 14:02:01 +0100 Subject: [PATCH 11/42] Update dependency for 8.5.5 (cherry picked from commit 2d3388d6d3ff6882c9e7b9e92f717ece03834fbe) # Conflicts: # src/SolutionInfo.cs --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 347bde139e..72619db02e 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ not want this to happen as the alpha of the next major is, really, the next major already. --> - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8f0db76c1d..cc6200cea7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -86,7 +86,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0111a36993..5173972f42 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -61,7 +61,7 @@ - + From 8752ce871aecab986aeb1aa54b992620786d94ad Mon Sep 17 00:00:00 2001 From: SimonCropp Date: Fri, 20 Mar 2020 23:25:32 +1100 Subject: [PATCH 12/42] fix assert order of expted V actual --- src/Umbraco.Tests/Cache/AppCacheTests.cs | 2 +- .../UmbracoSettings/ContentElementTests.cs | 44 +++++++++---------- .../CoreThings/XmlExtensionsTests.cs | 6 +-- .../Persistence/BulkDataReaderTests.cs | 4 +- .../AngularAntiForgeryTests.cs | 4 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Umbraco.Tests/Cache/AppCacheTests.cs b/src/Umbraco.Tests/Cache/AppCacheTests.cs index 3a86feb90a..f0ac9a2b0a 100644 --- a/src/Umbraco.Tests/Cache/AppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/AppCacheTests.cs @@ -96,7 +96,7 @@ namespace Umbraco.Tests.Cache return ""; }); - Assert.AreEqual(counter, 1); + Assert.AreEqual(1, counter); } diff --git a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs index 0245159c6e..33df3caaad 100644 --- a/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs +++ b/src/Umbraco.Tests/Configurations/UmbracoSettings/ContentElementTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public void EmailAddress() { - Assert.AreEqual(SettingsSection.Content.NotificationEmailAddress, "robot@umbraco.dk"); + Assert.AreEqual("robot@umbraco.dk", SettingsSection.Content.NotificationEmailAddress); } [Test] public virtual void DisableHtmlEmail() @@ -27,17 +27,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings [Test] public virtual void Can_Set_Multiple() { - Assert.AreEqual(SettingsSection.Content.Error404Collection.Count(), 3); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).Culture, "default"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId, 1047); + Assert.AreEqual(3, SettingsSection.Content.Error404Collection.Count()); + Assert.AreEqual("default", SettingsSection.Content.Error404Collection.ElementAt(0).Culture); + Assert.AreEqual(1047, SettingsSection.Content.Error404Collection.ElementAt(0).ContentId); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).Culture, "en-US"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']"); + Assert.AreEqual("en-US", SettingsSection.Content.Error404Collection.ElementAt(1).Culture); + Assert.AreEqual("$site/error [@name = 'error']", SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentId); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentKey); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).Culture, "en-UK"); - Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC")); + Assert.AreEqual("en-UK", SettingsSection.Content.Error404Collection.ElementAt(2).Culture); + Assert.AreEqual(new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"), SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey); Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentKey); Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentId); } @@ -47,23 +47,23 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings { Assert.IsTrue(SettingsSection.Content.ImageFileTypes.All(x => "jpeg,jpg,gif,bmp,png,tiff,tif".Split(',').Contains(x))); } - + [Test] public virtual void ImageAutoFillProperties() { - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.Count(), 2); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2"); - Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2"); + Assert.AreEqual(2, SettingsSection.Content.ImageAutoFillProperties.Count()); + Assert.AreEqual("umbracoFile", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias); + Assert.AreEqual("umbracoWidth", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias); + Assert.AreEqual("umbracoHeight", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias); + Assert.AreEqual("umbracoBytes", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias); + Assert.AreEqual("umbracoExtension", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias); + Assert.AreEqual("umbracoFile2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias); + Assert.AreEqual("umbracoWidth2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias); + Assert.AreEqual("umbracoHeight2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias); + Assert.AreEqual("umbracoBytes2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias); + Assert.AreEqual("umbracoExtension2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias); } - + [Test] public void PreviewBadge() { @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension); Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension); - Assert.AreEqual(SettingsSection.Content.IsFileAllowedForUpload(extension), expected); + Assert.AreEqual(expected, SettingsSection.Content.IsFileAllowedForUpload(extension)); } } } diff --git a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs index 42d8f92a5d..62e385beff 100644 --- a/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/XmlExtensionsTests.cs @@ -16,7 +16,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -27,7 +27,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); } [Test] @@ -41,7 +41,7 @@ namespace Umbraco.Tests.CoreThings var xmlNode = cdata.GetXmlNode(xdoc); - Assert.AreEqual(xmlNode.InnerText, "hello world"); + Assert.AreEqual("hello world", xmlNode.InnerText); Assert.AreEqual(xml, xdoc.OuterXml); } } diff --git a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs index 3ada9fcceb..c157d66b2c 100644 --- a/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs +++ b/src/Umbraco.Tests/Persistence/BulkDataReaderTests.cs @@ -1693,7 +1693,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.Depth, 0); + Assert.AreEqual(0, testReader.Depth); } } @@ -2067,7 +2067,7 @@ namespace Umbraco.Tests.Persistence { Assert.IsTrue(testReader.Read()); - Assert.AreEqual(testReader.RecordsAffected, -1); + Assert.AreEqual(-1, testReader.RecordsAffected); } } diff --git a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs index 780a3e90fd..fd30f1b5ec 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/AngularAntiForgeryTests.cs @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } @@ -45,7 +45,7 @@ namespace Umbraco.Tests.Web.AngularIntegration string cookieToken, headerToken; AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken); - Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); + Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken)); } } From ff2027df0731ad3a3d1b56e0c0b897bf1b31bf01 Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sat, 21 Mar 2020 16:49:14 +0100 Subject: [PATCH 13/42] Fixes #7798 : Invalid implementation of IEnumerable in Content Cache --- .../PublishedContent/NuCacheChildrenTests.cs | 15 ++++++++++++--- .../PublishedCache/NuCache/ContentCache.cs | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs index cc9ffdba3c..4221968429 100644 --- a/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs +++ b/src/Umbraco.Tests/PublishedContent/NuCacheChildrenTests.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Reflection; using Moq; using NUnit.Framework; using Umbraco.Core; @@ -27,7 +25,6 @@ using Umbraco.Web.Cache; using Umbraco.Web.PublishedCache; using Umbraco.Web.PublishedCache.NuCache; using Umbraco.Web.PublishedCache.NuCache.DataSource; -using Umbraco.Web.PublishedCache.NuCache.Snap; namespace Umbraco.Tests.PublishedContent { @@ -1312,6 +1309,18 @@ namespace Umbraco.Tests.PublishedContent AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1); } + [Test] + public void MultipleCacheIteration() + { + //see https://github.com/umbraco/Umbraco-CMS/issues/7798 + this.Init(this.GetInvariantKits()); + var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null); + this._snapshotAccessor.PublishedSnapshot = snapshot; + + var items = snapshot.Content.GetByXPath("/root/itype"); + Assert.AreEqual(items.Count(), items.Count()); + } + private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild) { Assert.AreEqual(parent, node.ParentContentId); diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index 84edb9113c..24c6a7018b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -355,6 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private static IEnumerable GetByXPath(XPathNodeIterator iterator) { + iterator = iterator.Clone(); while (iterator.MoveNext()) { var xnav = iterator.Current as NavigableNavigator; From 3edfc42623cc283f75c3b46bad35f0cdfb54734d Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Fri, 20 Mar 2020 17:42:10 +0000 Subject: [PATCH 14/42] Collapse navigation items based on container size - Previously a maximum of 8 items were shown. - This change calculates the width of each item and displays as many as possible in the container. - Any overflowing items will be moved to the ... menu --- .../application/umbsections.directive.js | 37 +++++++------------ .../components/application/umb-sections.html | 8 ++-- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index a33796ab6d..190da7da0d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -12,26 +12,25 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se var sectionItemsWidth = []; var evts = []; - var maxSections = 8; //setup scope vars - scope.maxSections = maxSections; - scope.overflowingSections = 0; scope.sections = []; + scope.visibleSections = 0; scope.currentSection = appState.getSectionState("currentSection"); - scope.showTray = false; //appState.getGlobalState("showTray"); + scope.showTray = false; scope.stickyNavigation = appState.getGlobalState("stickyNavigation"); - scope.needTray = false; function loadSections() { sectionService.getSectionsForUser() .then(function (result) { scope.sections = result; + scope.visibleSections = scope.sections.length; + // store the width of each section so we can hide/show them based on browser width // we store them because the sections get removed from the dom and then we // can't tell when to show them gain $timeout(function () { - $("#applications .sections li").each(function (index) { + $("#applications .sections li:not(:last)").each(function (index) { sectionItemsWidth.push($(this).outerWidth()); }); }); @@ -42,25 +41,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se function calculateWidth() { $timeout(function () { //total width minus room for avatar, search, and help icon - var windowWidth = $(window).width() - 150; + var containerWidth = $(".umb-app-header").outerWidth() - $(".umb-app-header__actions").outerWidth(); + var trayToggleWidth = $("#applications .sections li.expand").outerWidth(); var sectionsWidth = 0; - scope.totalSections = scope.sections.length; - scope.maxSections = maxSections; - scope.overflowingSections = scope.maxSections - scope.totalSections; - scope.needTray = scope.sections.length > scope.maxSections; - + // detect how many sections we can show on the screen for (var i = 0; i < sectionItemsWidth.length; i++) { var sectionItemWidth = sectionItemsWidth[i]; sectionsWidth += sectionItemWidth; - if (sectionsWidth > windowWidth) { - scope.needTray = true; - scope.maxSections = i - 1; - scope.overflowingSections = scope.maxSections - scope.totalSections; - break; + if (sectionsWidth + trayToggleWidth > containerWidth) { + scope.visibleSections = i; + return; } } + + scope.visibleSections = scope.sections.length; }); } @@ -134,14 +130,9 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se }; scope.currentSectionInOverflow = function () { - if (scope.overflowingSections === 0) { - return false; - } - var currentSection = scope.sections.filter(s => s.alias === scope.currentSection); - return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections); - + return currentSection.length > 0 && scope.sections.indexOf(currentSection[0]) > scope.visibleSections - 1; }; loadSections(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 0dac7176ee..d435c6ead7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -2,7 +2,7 @@
    - - - From b082d40921d3ccc939c28f1adda7973729b63b16 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 25 Mar 2020 02:48:14 +1000 Subject: [PATCH 20/42] =?UTF-8?q?adds=20utility.js=20as=20facade=20to=20ge?= =?UTF-8?q?neric=20javascript=20utility=20functio=E2=80=A6=20(#7738)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Umbraco.Web.UI.Client/.eslintrc | 4 +- src/Umbraco.Web.UI.Client/gulp/config.js | 4 +- .../common/services/angularhelper.service.js | 10 +-- .../src/main.controller.js | 11 ++- src/Umbraco.Web.UI.Client/src/utilities.js | 86 +++++++++++++++++++ .../views/documenttypes/edit.controller.js | 2 +- .../packages/views/created.controller.js | 2 +- .../partialviewmacros/edit.controller.js | 2 +- .../src/views/users/user.controller.js | 2 +- .../users/views/groups/groups.controller.js | 2 +- .../users/views/users/users.controller.js | 2 +- 11 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/utilities.js diff --git a/src/Umbraco.Web.UI.Client/.eslintrc b/src/Umbraco.Web.UI.Client/.eslintrc index 1bfa081b9a..a4010917fb 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc +++ b/src/Umbraco.Web.UI.Client/.eslintrc @@ -19,10 +19,10 @@ "tinyMCE": false, "FileReader": false, "Umbraco": false, + "Utilities": false, "window": false, "LazyLoad": false, "ActiveXObject": false, "Bloodhound": false } - -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI.Client/gulp/config.js b/src/Umbraco.Web.UI.Client/gulp/config.js index 59e8bf6c05..39dc9bb2a4 100755 --- a/src/Umbraco.Web.UI.Client/gulp/config.js +++ b/src/Umbraco.Web.UI.Client/gulp/config.js @@ -28,9 +28,9 @@ module.exports = { installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" }, filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" }, resources: { files: "./src/common/resources/**/*.js", out: "umbraco.resources.js" }, - services: { files: "./src/common/services/**/*.js", out: "umbraco.services.js" }, + services: { files: ["./src/common/services/**/*.js", "./src/utilities.js"], out: "umbraco.services.js" }, security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" }, - + //the controllers for views controllers: { files: [ diff --git a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js index 455857c1e1..8cba83328f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/angularhelper.service.js @@ -178,11 +178,11 @@ function angularHelper($q) { $valid: true, $submitted: false, $pending: undefined, - $addControl: angular.noop, - $removeControl: angular.noop, - $setValidity: angular.noop, - $setDirty: angular.noop, - $setPristine: angular.noop, + $addControl: Utilities.noop, + $removeControl: Utilities.noop, + $setValidity: Utilities.noop, + $setDirty: Utilities.noop, + $setPristine: Utilities.noop, $name: formName }; } diff --git a/src/Umbraco.Web.UI.Client/src/main.controller.js b/src/Umbraco.Web.UI.Client/src/main.controller.js index 883907d1dc..198ac132c1 100644 --- a/src/Umbraco.Web.UI.Client/src/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/main.controller.js @@ -1,19 +1,18 @@ - -/** +/** * @ngdoc controller - * @name Umbraco.MainController + * @name Umbraco.MainController * @function * - * @description + * @description * The main application controller * */ function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, navigationService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - + //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; + $scope.authenticated = null; $scope.touchDevice = appState.getGlobalState("touchDevice"); $scope.infiniteMode = false; $scope.overlay = {}; diff --git a/src/Umbraco.Web.UI.Client/src/utilities.js b/src/Umbraco.Web.UI.Client/src/utilities.js new file mode 100644 index 0000000000..bd12506358 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/utilities.js @@ -0,0 +1,86 @@ +/** + * A friendly utility collection to replace AngularJs' ng-functions + * If it doesn't exist here, it's probably available as vanilla JS + * + * Still carries a dependency on underscore, but if usages of underscore from + * elsewhere in the codebase can instead use these methods, the underscore + * dependency will be nicely abstracted and can be removed/swapped later + * + * This collection is open to extension... + */ +(function (window) { + + /** + * Equivalent to angular.noop + */ + const noop = () => { }; + + /** + * Facade to angular.copy + */ + const copy = val => angular.copy(val); + + /** + * Equivalent to angular.isArray + */ + const isArray = val => Array.isArray(val) || val instanceof Array; + + /** + * Facade to angular.equals + */ + const equals = (a, b) => angular.equals(a, b); + + /** + * Facade to angular.extend + * Use this with Angular objects, for vanilla JS objects, use Object.assign() + */ + const extend = (dst, src) => angular.extend(dst, src); + + /** + * Equivalent to angular.isFunction + */ + const isFunction = val => typeof val === 'function'; + + /** + * Equivalent to angular.isUndefined + */ + const isUndefined = val => typeof val === 'undefined'; + + /** + * Equivalent to angular.isDefined. Inverts result of const isUndefined + */ + const isDefined = val => !isUndefined(val); + + /** + * Equivalent to angular.isString + */ + const isString = val => typeof val === 'string'; + + /** + * Equivalent to angular.isNumber + */ + const isNumber = val => typeof val === 'number'; + + /** + * Equivalent to angular.isObject + */ + const isObject = val => val !== null && typeof val === 'object'; + + let _utilities = { + noop: noop, + copy: copy, + isArray: isArray, + equals: equals, + extend: extend, + isFunction: isFunction, + isUndefined: isUndefined, + isDefined: isDefined, + isString: isString, + isNumber: isNumber, + isObject: isObject + }; + + if (typeof (window.Utilities) === 'undefined') { + window.Utilities = _utilities; + } +})(window); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index b04cccbc0d..21eb77b1e9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -325,7 +325,7 @@ /* ---------- SAVE ---------- */ function save() { - saveInternal().then(angular.noop, angular.noop); + saveInternal().then(Utilities.noop, Utilities.noop); } /** This internal save method performs the actual saving and returns a promise, not to be bound to any buttons but used by other bound methods */ diff --git a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js index 295057e609..503d85fe8d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packages/views/created.controller.js @@ -15,7 +15,7 @@ packageResource.getAllCreated().then(createdPackages => { vm.createdPackages = createdPackages; - }, angular.noop); + }, Utilities.noop); } diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index aa37a91919..2f7d879607 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -80,7 +80,7 @@ activate: false }); completeSave(saved); - }, angular.noop); + }, Utilities.noop); } else { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 9534eea9fd..56d01a8604 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -168,7 +168,7 @@ extendedSave(saved).then(function(result) { //if all is good, then reset the form formHelper.resetForm({ scope: $scope }); - }, angular.noop); + }, Utilities.noop); vm.user = _.omit(saved, "navigation"); //restore diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js index 984866dca1..31c52a344b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/groups/groups.controller.js @@ -112,7 +112,7 @@ userGroupsResource.deleteUserGroups(_.pluck(vm.selection, "id")).then(function (data) { clearSelection(); onInit(); - }, angular.noop); + }, Utilities.noop); overlayService.close(); } }; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js index f3c28fdb9d..4ac385921e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/users/users.controller.js @@ -386,7 +386,7 @@ vm.selectedBulkUserGroups = []; editorService.close(); clearSelection(); - }, angular.noop); + }, Utilities.noop); }, close: function () { vm.selectedBulkUserGroups = []; From 994c379a946fa7e36eed669e429ab26fa977b00c Mon Sep 17 00:00:00 2001 From: Matthew Wise <6782865+Matthew-Wise@users.noreply.github.com> Date: Wed, 4 Mar 2020 10:36:49 +0000 Subject: [PATCH 21/42] Removed duplications of tree routing --- .../src/common/services/navigation.service.js | 49 +++++++++++-------- src/Umbraco.Web.UI.Client/src/routes.js | 44 ++--------------- 2 files changed, 33 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 8d1caab850..ab9cfb63d2 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -578,30 +578,12 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (args.action.metaData["actionView"]) { templateUrl = args.action.metaData["actionView"]; } - else { - - //by convention we will look into the /views/{treetype}/{action}.html - // for example: /views/content/create.html - - //we will also check for a 'packageName' for the current tree, if it exists then the convention will be: - // for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html - + else { var treeAlias = treeService.getTreeAlias(args.node); - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); - if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; - } - - if (packageTreeFolder) { - templateUrl = Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + treeAlias + "/" + args.action.alias + ".html"; - } - else { - templateUrl = "views/" + treeAlias + "/" + args.action.alias + ".html"; - } - + } + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); @@ -611,6 +593,31 @@ function navigationService($routeParams, $location, $q, $injector, eventsService } }, + /** + * @ngdoc method + * @name umbraco.services.navigationService#getTreeTemplateUrl + * @methodOf umbraco.services.navigationService + * + * @param {string} treeAlias the alias of the tree to look up + * @param {string} action the view file name + * @description + * creates the templateUrl based on treeAlias and action + * by convention we will look into the /views/{treetype}/{action}.html + * for example: /views/content/create.html + * we will also check for a 'packageName' for the current tree, if it exists then the convention will be: + * for example: /App_Plugins/{mypackage}/backoffice/{treetype}/create.html + */ + getTreeTemplateUrl: function(treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); + if (packageTreeFolder) { + return Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + + "/" + packageTreeFolder + + "/backoffice/" + treeAlias + "/" + action + ".html"; + } + else { + return "views/" + treeAlias + "/" + action + ".html"; + } + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index 556a4d6aef..93122792d3 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -154,7 +154,7 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
    ", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; @@ -176,24 +176,7 @@ app.config(function ($routeProvider) { $scope.templateUrl = "views/users/overview.html"; return; } - - // Here we need to figure out if this route is for a user's package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -202,30 +185,13 @@ app.config(function ($routeProvider) { //This allows us to dynamically change the template for this route since you cannot inject services into the templateUrl method. template: "
    ", //This controller will execute for this route, then we replace the template dynamically based on the current tree. - controller: function ($scope, $route, $routeParams, treeService) { + controller: function ($scope, $routeParams, navigationService) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; + return; } - - // Here we need to figure out if this route is for a package tree and if so then we need - // to change it's convention view path to: - // /App_Plugins/{mypackage}/backoffice/{treetype}/{method}.html - - // otherwise if it is a core tree we use the core paths: - // views/{treetype}/{method}.html - - var packageTreeFolder = treeService.getTreePackageFolder($routeParams.tree); - - if (packageTreeFolder) { - $scope.templateUrl = (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + - "/" + packageTreeFolder + - "/backoffice/" + $routeParams.tree + "/" + $routeParams.method + ".html"); - } - else { - $scope.templateUrl = ('views/' + $routeParams.tree + '/' + $routeParams.method + '.html'); - } - + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, From 7c841eb6ac627ca4c632ce365b76af36dce98eab Mon Sep 17 00:00:00 2001 From: Matt Barlow Date: Thu, 27 Feb 2020 12:59:34 +0100 Subject: [PATCH 22/42] 7728 - show elipsis only when > length Fixed the elipsis issue, only adds an elipisis if the text length exceeds the length that is being truncated too. --- src/Umbraco.Web/HtmlStringUtilities.cs | 28 ++++++++++++++++---------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/HtmlStringUtilities.cs b/src/Umbraco.Web/HtmlStringUtilities.cs index 4606a58a3a..a8cbb70019 100644 --- a/src/Umbraco.Web/HtmlStringUtilities.cs +++ b/src/Umbraco.Web/HtmlStringUtilities.cs @@ -243,21 +243,27 @@ namespace Umbraco.Web } } - if (!lengthReached && currentTextLength >= length) + if (!lengthReached) { - // if the last character added was the first of a two character unicode pair, add the second character - if (Char.IsHighSurrogate((char)ic)) + if (currentTextLength == length) { - var lowSurrogate = tr.Read(); - outputtw.Write((char)lowSurrogate); - } + // if the last character added was the first of a two character unicode pair, add the second character + if (char.IsHighSurrogate((char)ic)) + { + var lowSurrogate = tr.Read(); + outputtw.Write((char)lowSurrogate); + } - // Reached truncate limit. - if (addElipsis) - { - outputtw.Write(hellip); } - lengthReached = true; + // only add elipsis if current length greater than original length + if (currentTextLength > length) + { + if (addElipsis) + { + outputtw.Write(hellip); + } + lengthReached = true; + } } } From ffef6ed2a1e9dbad6f8237668ea7fd8e4c373672 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Wed, 25 Mar 2020 03:04:38 +1000 Subject: [PATCH 23/42] 7713 - changing CSS directory breaks RTE (#7726) --- .../Implement/StylesheetRepository.cs | 11 ++++++++++- .../Repositories/StylesheetRepositoryTest.cs | 16 +++++++--------- .../rte/rte.prevalues.controller.js | 10 ++++++++-- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs index 4c02a8f4b5..698a9f4364 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/StylesheetRepository.cs @@ -25,8 +25,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement path = path.EnsureEndsWith(".css"); - if (FileSystem.FileExists(path) == false) + // if the css directory is changed, references to the old path can still exist (ie in RTE config) + // these old references will throw an error, which breaks the RTE + // try-catch here makes the request fail silently, and allows RTE to load correctly + try + { + if (FileSystem.FileExists(path) == false) + return null; + } catch + { return null; + } // content will be lazy-loaded when required var created = FileSystem.GetCreated(path).UtcDateTime; diff --git a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs index f427f22796..304422d1e2 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/StylesheetRepositoryTest.cs @@ -294,15 +294,13 @@ namespace Umbraco.Tests.Persistence.Repositories stylesheet = repository.Get("missing.css"); Assert.IsNull(stylesheet); - // fixed in 7.3 - 7.2.8 used to... - Assert.Throws(() => - { - stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist - }); - Assert.Throws(() => - { - stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists - }); + // #7713 changes behaviour to return null when outside the filesystem + // to accomodate changing the CSS path and not flooding the backoffice with errors + stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist + Assert.IsNull(stylesheet); + + stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists + Assert.IsNull(stylesheet); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js index 41097f9e9a..47b0215dac 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.controller.js @@ -39,8 +39,14 @@ angular.module("umbraco").controller("Umbraco.PrevalueEditors.RteController", stylesheetResource.getAll().then(function(stylesheets){ $scope.stylesheets = stylesheets; - - _.each($scope.stylesheets, function (stylesheet) { + + // if the CSS directory changes, previously assigned stylesheets are retained, but will not be visible + // and will throw a 404 when loading the RTE. Remove them here. Still needs to be saved... + let cssPath = Umbraco.Sys.ServerVariables.umbracoSettings.cssPath; + $scope.model.value.stylesheets = $scope.model.value.stylesheets + .filter(sheet => sheet.startsWith(cssPath)); + + $scope.stylesheets.forEach(stylesheet => { // support both current format (full stylesheet path) and legacy format (stylesheet name only) stylesheet.selected = $scope.model.value.stylesheets.indexOf(stylesheet.path) >= 0 ||$scope.model.value.stylesheets.indexOf(stylesheet.name) >= 0; }); From 2ec08610518ea2cdc9a03d8c84d95530eba0365c Mon Sep 17 00:00:00 2001 From: Joe Delly Date: Tue, 24 Mar 2020 13:35:11 -0400 Subject: [PATCH 24/42] Fix for exception throw when validating URLs (#7701) --- src/Umbraco.Core/StringExtensions.cs | 15 +++++++++++++++ .../Strings/StringExtensionsTests.cs | 18 ++++++++++++++++++ .../PropertyEditors/UploadFileTypeValidator.cs | 2 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 4451fdbba7..c37f8bdf35 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -79,6 +79,21 @@ namespace Umbraco.Core } + /// + /// Determines the extension of the path or URL + /// + /// + /// Extension of the file + public static string GetFileExtension(this string file) + { + //Find any characters between the last . and the start of a query string or the end of the string + const string pattern = @"(?\.[^\.\?]+)(\?.*|$)"; + var match = Regex.Match(file, pattern); + return match.Success + ? match.Groups["extension"].Value + : string.Empty; + } + ///
    /// Based on the input string, this will detect if the string is a JS path or a JS snippet. /// If a path cannot be determined, then it is assumed to be a snippet the original text is returned diff --git a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs index 4c365d733f..39ffeed205 100644 --- a/src/Umbraco.Tests/Strings/StringExtensionsTests.cs +++ b/src/Umbraco.Tests/Strings/StringExtensionsTests.cs @@ -70,6 +70,24 @@ namespace Umbraco.Tests.Strings Assert.AreEqual(stripped, result); } + [TestCase("../wtf.js?x=wtf", ".js")] + [TestCase(".htaccess", ".htaccess")] + [TestCase("path/to/file/image.png", ".png")] + [TestCase("c:\\abc\\def\\ghi.jkl", ".jkl")] + [TestCase("/root/folder.name/file.ext", ".ext")] + [TestCase("http://www.domain.com/folder/name/file.txt", ".txt")] + [TestCase("i/don't\\have\\an/extension", "")] + [TestCase("https://some.where/path/to/file.ext?query=this&more=that", ".ext")] + [TestCase("double_query.string/file.ext?query=abc?something.else", ".ext")] + [TestCase("test.tar.gz", ".gz")] + [TestCase("wierd.file,but._legal", "._legal")] + [TestCase("one_char.x", ".x")] + public void Get_File_Extension(string input, string result) + { + var extension = input.GetFileExtension(); + Assert.AreEqual(result, extension); + } + [TestCase("'+alert(1234)+'", "+alert1234+")] [TestCase("'+alert(56+78)+'", "+alert56+78+")] [TestCase("{{file}}", "file")] diff --git a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs index 6855ab3bb8..8d7bd29889 100644 --- a/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs +++ b/src/Umbraco.Web/PropertyEditors/UploadFileTypeValidator.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.PropertyEditors internal static bool IsValidFileExtension(string fileName) { if (fileName.IndexOf('.') <= 0) return false; - var extension = new FileInfo(fileName).Extension.TrimStart("."); + var extension = fileName.GetFileExtension().TrimStart("."); return Current.Configs.Settings().Content.IsFileAllowedForUpload(extension); } } From 17d91da8d1444c8f2ce4c48380ead0c89bf9e65a Mon Sep 17 00:00:00 2001 From: Chris Oliver Date: Wed, 25 Mar 2020 16:29:42 +0000 Subject: [PATCH 25/42] Remove button border radius when used in a button group (#7834) --- .../src/less/components/buttons/umb-button-group.less | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less index 0465881387..02b67460f6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button-group.less @@ -17,15 +17,17 @@ .umb-button-group { .umb-button__button { - border-radius: @baseBorderRadius; + border-radius: @baseBorderRadius 0 0 @baseBorderRadius; + + &:hover { + z-index: 2; + } } .umb-button-group__toggle { border-radius: 0 @baseBorderRadius @baseBorderRadius 0; - border-left: 1px solid rgba(0,0,0,0.09); - margin-left: -2px; + margin-left: -1px; padding-left: 10px; padding-right: 10px; } - } From ec64da080460d85176960289dd7c57d75511a570 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Tue, 11 Feb 2020 16:39:18 +0000 Subject: [PATCH 26/42] Grant access to Packages section for any user groups with access to the Developer section --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../V_8_0_0/AddPackagesSectionAccess.cs | 25 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + 3 files changed, 27 insertions(+) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 7143be2b90..f0cfc08281 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -166,6 +166,7 @@ namespace Umbraco.Core.Migrations.Upgrade .As("{0576E786-5C30-4000-B969-302B61E90CA3}"); To("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}"); + To("{DF470D86-E5CA-42AC-9780-9D28070E25F9}"); // finish migrating from v7 - recreate all keys and indexes To("{3F9764F5-73D0-4D45-8804-1240A66E43A2}"); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs new file mode 100644 index 0000000000..d44e637a2c --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddPackagesSectionAccess.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class AddPackagesSectionAccess : MigrationBase + { + public AddPackagesSectionAccess(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // Any user group which had access to the Developer section should have access to Packages + Database.Execute($@" + insert into {Constants.DatabaseSchema.Tables.UserGroup2App} + select userGroupId, '{Constants.Applications.Packages}' + from {Constants.DatabaseSchema.Tables.UserGroup2App} + where app='developer'"); + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 30247bed8a..24954e316c 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -256,6 +256,7 @@ + From 935690b727c395d7a451d6f23890c45d14c6ecbd Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 25 Mar 2020 18:09:03 +0100 Subject: [PATCH 27/42] Bump `caniuse-lite` dependency --- src/Umbraco.Web.UI.Client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index c150af79de..438052367e 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -50,7 +50,7 @@ "@babel/core": "7.6.4", "@babel/preset-env": "7.6.3", "autoprefixer": "9.6.5", - "caniuse-lite": "^1.0.30001002", + "caniuse-lite": "^1.0.30001037", "cssnano": "4.1.10", "fs": "0.0.2", "gulp": "4.0.2", From 972acc6718ce418e648ed4ac9ef2527f47c1aa33 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Thu, 26 Mar 2020 08:39:38 +0100 Subject: [PATCH 28/42] Installer adjustments (#7592) --- .../src/installer/installer.service.js | 25 +++++++++---------- .../installer/steps/database.controller.js | 17 +++++++------ .../installer/steps/machinekey.controller.js | 3 +-- .../src/installer/steps/starterkit.html | 7 +++--- .../src/installer/steps/user.controller.js | 12 ++++----- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index f6f162f04f..05c391c3e7 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -2,17 +2,16 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco var _status = { index: 0, - current: undefined, - steps: undefined, + current: null, + steps: null, loading: true, progress: "100%" }; - var factTimer = undefined; + var factTimer; var _installerModel = { - installId: undefined, - instructions: { - } + installId: null, + instructions: {} }; //add to umbraco installer facts here @@ -304,7 +303,7 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco }, switchToFeedback : function(){ - service.status.current = undefined; + service.status.current = null; service.status.loading = true; service.status.configuring = false; @@ -320,11 +319,11 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco switchToConfiguration : function(){ service.status.loading = false; service.status.configuring = true; - service.status.feedback = undefined; - service.status.fact = undefined; + service.status.feedback = null; + service.status.fact = null; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } }, @@ -335,8 +334,8 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco service.status.feedback = "Redirecting you to Umbraco, please wait"; service.status.loading = false; - if(factTimer){ - clearInterval(factTimer); + if (factTimer) { + clearInterval(factTimer); } $timeout(function(){ diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index e83c5114fc..687fce95ae 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,18 +4,19 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.invalidDbDns = false; $scope.dbs = [ - { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, - { name: 'Microsoft SQL Server', id: 1}, + { name: 'Microsoft SQL Server Compact (SQL CE)', id: 0 }, + { name: 'Microsoft SQL Server', id: 1 }, { name: 'Microsoft SQL Azure', id: 3 }, - { name: 'Custom connection string', id: -1} + { name: 'Custom connection string', id: -1 } ]; - if ( installerService.status.current.model.dbType === undefined ) { + if (angular.isUndefined(installerService.status.current.model.dbType) || installerService.status.current.model.dbType === null) { installerService.status.current.model.dbType = 0; } - $scope.validateAndForward = function(){ - if ( !$scope.checking && this.myForm.$valid ) { + $scope.validateAndForward = function() { + if (!$scope.checking && this.myForm.$valid) + { $scope.checking = true; $scope.invalidDbDns = false; @@ -23,9 +24,9 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $http.post( Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection", - model ).then( function( response ) { + model).then(function(response) { - if ( response.data === true ) { + if (response.data === true) { installerService.forward(); } else { diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js index bdcef63d95..fdd2c65c1c 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/machinekey.controller.js @@ -1,5 +1,4 @@ angular.module("umbraco.install").controller("Umbraco.Installer.MachineKeyController", function ($scope, installerService) { - $scope.continue = function () { installerService.status.current.model = true; @@ -11,4 +10,4 @@ installerService.forward(); }; -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html index cfb08744b2..92f5cc8d9d 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/starterkit.html @@ -20,8 +20,9 @@ - + diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js index 10c0d596eb..9f0108853f 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/user.controller.js @@ -13,14 +13,14 @@ angular.module("umbraco.install").controller("Umbraco.Install.UserController", f $scope.passwordPattern = new RegExp(exp); } - $scope.validateAndInstall = function(){ - installerService.install(); + $scope.validateAndInstall = function() { + installerService.install(); }; $scope.validateAndForward = function(){ - if(this.myForm.$valid){ - installerService.forward(); - } + if (this.myForm.$valid) { + installerService.forward(); + } }; -}); \ No newline at end of file +}); From a0e5b839141dd3d63a063656ac5f3ebc0f4aca92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Gregersen?= Date: Wed, 25 Mar 2020 19:23:29 +0100 Subject: [PATCH 29/42] Add document to downloadFile-method Simple copy/paste to document a useful method --- .../common/services/umbrequesthelper.service.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js index 6c765ee135..0d766dc7d8 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/umbrequesthelper.service.js @@ -365,12 +365,23 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe }, /** + * @ngdoc method + * @name umbraco.resources.contentResource#downloadFile + * @methodOf umbraco.resources.contentResource + * + * @description * Downloads a file to the client using AJAX/XHR - * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html - * See https://stackoverflow.com/a/24129082/694494 + * + * @param {string} httpPath the path (url) to the resource being downloaded + * @returns {Promise} http promise object. */ downloadFile : function (httpPath) { + /** + * Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html + * See https://stackoverflow.com/a/24129082/694494 + */ + // Use an arraybuffer return $http.get(httpPath, { responseType: 'arraybuffer' }) .then(function (response) { From 6fef85aafde5eb6614c9831034ac1a9cc11eabf6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Mar 2020 22:03:26 +1100 Subject: [PATCH 30/42] fixes Memory Leak in GetCacheItem cache dependency #7773 --- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 37 +++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index 044aab4c42..87f0e75845 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -37,23 +37,28 @@ namespace Umbraco.Core.Cache /// public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - return Get(key, factory, timeout, isSliding, priority, removedCallback, dependency); + return Get(key, factory, timeout, isSliding, priority, removedCallback, + // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency + // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing + // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: + // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 + // Init is called as part of the ctor!! yikes. + // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 + () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); } /// public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - CacheDependency dependency = null; - if (dependentFiles != null && dependentFiles.Any()) - { - dependency = new CacheDependency(dependentFiles); - } - Insert(key, factory, timeout, isSliding, priority, removedCallback, dependency); + + Insert(key, factory, timeout, isSliding, priority, removedCallback, + // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency + // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing + // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: + // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 + // Init is called as part of the ctor!! yikes. + // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 + () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); } #region Dictionary @@ -103,7 +108,7 @@ namespace Umbraco.Core.Cache #endregion - private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) { key = GetCacheKey(key); @@ -164,7 +169,7 @@ namespace Umbraco.Core.Cache lck.UpgradeToWriteLock(); //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); + _cache.Insert(key, result, dependency(), absolute, sliding, priority, removedCallback); } } @@ -180,7 +185,7 @@ namespace Umbraco.Core.Cache return value; } - private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null) + private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) { // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. @@ -198,7 +203,7 @@ namespace Umbraco.Core.Cache { _locker.EnterWriteLock(); //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); + _cache.Insert(cacheKey, result, dependency(), absolute, sliding, priority, removedCallback); } finally { From a855a90a8a5b06eff0a91008e64d446379fb94ca Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 26 Mar 2020 22:09:49 +1100 Subject: [PATCH 31/42] cleaner fix --- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 35 ++++++++------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index 87f0e75845..bec596b129 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -37,28 +37,13 @@ namespace Umbraco.Core.Cache /// public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - return Get(key, factory, timeout, isSliding, priority, removedCallback, - // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency - // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing - // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: - // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 - // Init is called as part of the ctor!! yikes. - // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 - () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); + return GetInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles); } /// public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { - - Insert(key, factory, timeout, isSliding, priority, removedCallback, - // NOTE: We don't want to allocate an object if it isn't going to be used so we create the CacheDependency - // in a callback if it's needed ... but more importantly and we didn't anticipate this, just constructing - // a CacheDependency object is expensive and allocates a bunch of stuff, just check the code out for it: - // https://referencesource.microsoft.com/#system.web/Cache/CacheDependency.cs,304 - // Init is called as part of the ctor!! yikes. - // This change fixes https://github.com/umbraco/Umbraco-CMS/issues/7773 - () => dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null); + InsertInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles); } #region Dictionary @@ -108,7 +93,7 @@ namespace Umbraco.Core.Cache #endregion - private object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) + private object GetInternal(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { key = GetCacheKey(key); @@ -168,8 +153,12 @@ namespace Umbraco.Core.Cache var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration); lck.UpgradeToWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(key, result, dependency(), absolute, sliding, priority, removedCallback); + _cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback); } } @@ -185,7 +174,7 @@ namespace Umbraco.Core.Cache return value; } - private void Insert(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, Func dependency = null) + private void InsertInternal(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) { // NOTE - here also we must insert a Lazy but we can evaluate it right now // and make sure we don't store a null value. @@ -202,8 +191,12 @@ namespace Umbraco.Core.Cache try { _locker.EnterWriteLock(); + + // create a cache dependency if one is needed. + var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null; + //NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update! - _cache.Insert(cacheKey, result, dependency(), absolute, sliding, priority, removedCallback); + _cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback); } finally { From 42ddd90fd71883a3ae6c01bf2c6bbda17b610f25 Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 26 Mar 2020 15:57:15 +0000 Subject: [PATCH 32/42] Allow Macro's to be cached differently across variants (#7555) --- src/Umbraco.Web/Macros/MacroRenderer.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Macros/MacroRenderer.cs b/src/Umbraco.Web/Macros/MacroRenderer.cs index 8d7d3bc013..b4fd8c0d86 100755 --- a/src/Umbraco.Web/Macros/MacroRenderer.cs +++ b/src/Umbraco.Web/Macros/MacroRenderer.cs @@ -40,12 +40,18 @@ namespace Umbraco.Web.Macros #region MacroContent cache // gets this macro content cache identifier - private string GetContentCacheIdentifier(MacroModel model, int pageId) + private string GetContentCacheIdentifier(MacroModel model, int pageId, string cultureName) { var id = new StringBuilder(); var alias = model.Alias; id.AppendFormat("{0}-", alias); + //always add current culture to the key to allow variants to have different cache results + if (!string.IsNullOrEmpty(cultureName)) + { + // are there any unusual culture formats we'd need to handle? + id.AppendFormat("{0}-", cultureName); + } if (model.CacheByPage) id.AppendFormat("{0}-", pageId); @@ -190,7 +196,7 @@ namespace Umbraco.Web.Macros ? macroParams[key]?.ToString() ?? string.Empty : string.Empty; } - } + } #endregion #region Render/Execute @@ -221,7 +227,8 @@ namespace Umbraco.Web.Macros foreach (var prop in macro.Properties) prop.Value = ParseAttribute(pageElements, prop.Value); - macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id); + var cultureName = System.Threading.Thread.CurrentThread.CurrentUICulture.Name; + macro.CacheIdentifier = GetContentCacheIdentifier(macro, content.Id, cultureName); // get the macro from cache if it is there var macroContent = GetMacroContentFromCache(macro); @@ -317,7 +324,7 @@ namespace Umbraco.Web.Macros private Attempt ExecuteMacroOfType(MacroModel model, IPublishedContent content) { if (model == null) throw new ArgumentNullException(nameof(model)); - + // ensure that we are running against a published node (ie available in XML) // that may not be the case if the macro is embedded in a RTE of an unpublished document @@ -453,9 +460,9 @@ namespace Umbraco.Web.Macros return value; } - + #endregion - + } } From 50631c2b2894168915c7851ce2c5cc4d8ae59360 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sat, 28 Mar 2020 20:54:10 +0100 Subject: [PATCH 33/42] Migrated member group unit tests to new unit test project using builder pattern. --- .../Builders/ChildBuilderBase.cs | 1 - .../Builders/DataTypeBuilder.cs | 7 ++ .../Builders/Extensions/BuilderExtensions.cs | 7 ++ .../Builders/GenericDictionaryBuilder.cs | 26 ++++++ .../Interfaces/IWithCreatorIdBuilder.cs | 9 ++ .../Builders/MemberGroupBuilder.cs | 90 +++++++++++++++++++ .../Models/DataTypeTests.cs | 2 +- .../Models/MemberGroupTests.cs | 48 +++++----- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 9 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs rename src/{Umbraco.Tests => Umbraco.Tests.UnitTests/Umbraco.Infrastructure}/Models/MemberGroupTests.cs (61%) diff --git a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs index e1436ac1fe..fcd9691e1f 100644 --- a/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs +++ b/src/Umbraco.Tests.Common/Builders/ChildBuilderBase.cs @@ -9,7 +9,6 @@ namespace Umbraco.Tests.Common.Builders _parentBuilder = parentBuilder; } - public TParent Done() { return _parentBuilder; diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 20dc1bab81..04a464e5bd 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -8,6 +8,7 @@ namespace Umbraco.Tests.Common.Builders : BuilderBase, IWithIdBuilder, IWithKeyBuilder, + IWithCreatorIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder, IWithDeleteDateBuilder, @@ -133,6 +134,12 @@ namespace Umbraco.Tests.Common.Builders set => _key = value; } + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + DateTime? IWithCreateDateBuilder.CreateDate { get => _createDate; diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index c8f3f80bf1..6a2407ae53 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -12,6 +12,13 @@ namespace Umbraco.Tests.Common.Builders.Extensions return builder; } + public static T WithCreatorId(this T builder, int creatorId) + where T : IWithCreatorIdBuilder + { + builder.CreatorId = creatorId; + return builder; + } + public static T WithCreateDate(this T builder, DateTime createDate) where T : IWithCreateDateBuilder { diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs new file mode 100644 index 0000000000..0cf2284a49 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Umbraco.Tests.Common.Builders +{ + public class GenericDictionaryBuilder + : ChildBuilderBase> + { + private readonly IDictionary _dictionary; + + public GenericDictionaryBuilder(TBuilder parentBuilder) : base(parentBuilder) + { + _dictionary = new Dictionary(); + } + + public override IDictionary Build() + { + return new Dictionary(); + } + + public GenericDictionaryBuilder AddKeyValue(TKey key, TValue value) + { + _dictionary.Add(key, value); + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs new file mode 100644 index 0000000000..ae7712cf9e --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithCreatorIdBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithCreatorIdBuilder + { + int? CreatorId { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs new file mode 100644 index 0000000000..6092c79abb --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberGroupBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder + { + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _creatorId; + private IDictionary _additionalData = new Dictionary(); + + private GenericDictionaryBuilder _additionalDataBuilder; + + public GenericDictionaryBuilder AddAdditionalData() + { + var builder = new GenericDictionaryBuilder(this); + _additionalDataBuilder = builder; + return builder; + } + + public override MemberGroup Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var name = _name ?? Guid.NewGuid().ToString(); + var creatorId = _creatorId ?? 1; + + return new MemberGroup + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + Name = name, + CreatorId = creatorId, + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index a097661a93..4be32a9b4f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -10,8 +10,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models [TestFixture] public class DataTypeTests { - private readonly DataTypeBuilder _builder = new DataTypeBuilder(); + [Test] public void Can_Deep_Clone() { diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs similarity index 61% rename from src/Umbraco.Tests/Models/MemberGroupTests.cs rename to src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index c3cf1c8c76..158a70598c 100644 --- a/src/Umbraco.Tests/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -3,28 +3,21 @@ using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; -using Umbraco.Core.Serialization; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; -namespace Umbraco.Tests.Models +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { [TestFixture] public class MemberGroupTests { + private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); + [Test] public void Can_Deep_Clone() { // Arrange - var group = new MemberGroup() - { - CreateDate = DateTime.Now, - CreatorId = 4, - Id = 6, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - Name = "asdf" - }; - group.AdditionalData.Add("test1", 123); - group.AdditionalData.Add("test2", "hello"); + var group = BuildMemberGroup(); // Act var clone = (MemberGroup)group.DeepClone(); @@ -44,29 +37,32 @@ namespace Umbraco.Tests.Models //This double verifies by reflection var allProps = clone.GetType().GetProperties(); foreach (var propertyInfo in allProps) - { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(group, null)); - } } [Test] public void Can_Serialize_Without_Error() { - var group = new MemberGroup() - { - CreateDate = DateTime.Now, - CreatorId = 4, - Id = 6, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - Name = "asdf" - }; - group.AdditionalData.Add("test1", 123); - group.AdditionalData.Add("test2", "hello"); + var group = BuildMemberGroup(); var json = JsonConvert.SerializeObject(group); Debug.Print(json); } + private MemberGroup BuildMemberGroup() + { + return _builder + .WithId(6) + .WithKey(Guid.NewGuid()) + .WithName("asdf") + .WithCreatorId(4) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) + .AddAdditionalData() + .AddKeyValue("test1", 123) + .AddKeyValue("test2", "hello") + .Done() + .Build(); + } } } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 06f6a98573..eb500bf7e3 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -298,7 +298,6 @@ - From dface90de7f9a56a95a80f29d9eabd08571dbfd6 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Sun, 29 Mar 2020 16:17:06 +0200 Subject: [PATCH 34/42] Migrated member unit tests to new unit test project using builder pattern. --- .../Builders/ConfigurationEditorBuilder.cs | 2 +- .../Builders/DataTypeBuilder.cs | 73 +++-- .../Builders/DictionaryTranslationBuilder.cs | 1 - .../Builders/Extensions/BuilderExtensions.cs | 56 ++++ .../Builders/Extensions/StringExtensions.cs | 20 ++ .../Builders/GenericCollectionBuilder.cs | 26 ++ .../Builders/GenericDictionaryBuilder.cs | 4 +- .../Interfaces/IWithDescriptionBuilder.cs | 7 + .../Builders/Interfaces/IWithIconBuilder.cs | 7 + .../Builders/Interfaces/IWithLevelBuilder.cs | 7 + .../Interfaces/IWithParentIdBuilder.cs | 7 + .../Builders/Interfaces/IWithPathBuilder.cs | 7 + .../Interfaces/IWithSortOrderBuilder.cs | 7 + .../Interfaces/IWithThumbnailBuilder.cs | 7 + .../Interfaces/IWithTrashedBuilder.cs | 7 + .../Builders/MemberBuilder.cs | 280 ++++++++++++++++++ .../Builders/MemberGroupBuilder.cs | 19 +- .../Builders/MemberTypeBuilder.cs | 198 +++++++++++++ .../Builders/PropertyGroupBuilder.cs | 60 ++++ .../Builders/PropertyTypeBuilder.cs | 93 ++++++ .../Models/MemberGroupTests.cs | 4 +- .../Models/MemberTests.cs | 154 ++++++++++ src/Umbraco.Tests/Models/MemberTests.cs | 134 --------- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 - 24 files changed, 998 insertions(+), 183 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs create mode 100644 src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/MemberBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs delete mode 100644 src/Umbraco.Tests/Models/MemberTests.cs diff --git a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs index 7789090d16..3e0b9b96a9 100644 --- a/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ConfigurationEditorBuilder.cs @@ -21,7 +21,7 @@ namespace Umbraco.Tests.Common.Builders public override IConfigurationEditor Build() { - var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); return new ConfigurationEditor() { diff --git a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs index 04a464e5bd..de7b8d9a97 100644 --- a/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DataTypeBuilder.cs @@ -12,7 +12,12 @@ namespace Umbraco.Tests.Common.Builders IWithCreateDateBuilder, IWithUpdateDateBuilder, IWithDeleteDateBuilder, - IWithNameBuilder + IWithNameBuilder, + IWithParentIdBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder { private readonly DataEditorBuilder _dataEditorBuilder; private int? _id; @@ -35,54 +40,18 @@ namespace Umbraco.Tests.Common.Builders _dataEditorBuilder = new DataEditorBuilder(this); } - public DataTypeBuilder WithParentId(int parentId) - { - _parentId = parentId; - return this; - } - - public DataTypeBuilder WithTrashed(bool trashed) - { - _trashed = trashed; - return this; - } - // public DataTypeBuilder WithConfiguration(object configuration) // { // _configuration = configuration; // return this; // } - public DataTypeBuilder WithLevel(int level) - { - _level = level; - return this; - } - - public DataTypeBuilder WithPath(string path) - { - _path = path; - return this; - } - - public DataTypeBuilder WithCreatorId(int creatorId) - { - _creatorId = creatorId; - return this; - } - public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) { _databaseType = databaseType; return this; } - public DataTypeBuilder WithSortOrder(int sortOrder) - { - _sortOrder = sortOrder; - return this; - } - public DataEditorBuilder AddEditor() { return _dataEditorBuilder; @@ -163,5 +132,35 @@ namespace Umbraco.Tests.Common.Builders get => _name; set => _name = value; } + + int? IWithParentIdBuilder.ParentId + { + get => _parentId; + set => _parentId = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + + int? IWithLevelBuilder.Level + { + get => _level; + set => _level = value; + } + + string IWithPathBuilder.Path + { + get => _path; + set => _path = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs index 37fb7c5b07..d2f9d4bf02 100644 --- a/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/DictionaryTranslationBuilder.cs @@ -21,7 +21,6 @@ namespace Umbraco.Tests.Common.Builders private DateTime? _updateDate; private string _value; - public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) { _languageBuilder = new LanguageBuilder(this); diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index 6a2407ae53..cce3b88470 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -53,5 +53,61 @@ namespace Umbraco.Tests.Common.Builders.Extensions builder.Key = key; return builder; } + + public static T WithParentId(this T builder, int parentId) + where T : IWithParentIdBuilder + { + builder.ParentId = parentId; + return builder; + } + + public static T WithTrashed(this T builder, bool trashed) + where T : IWithTrashedBuilder + { + builder.Trashed = trashed; + return builder; + } + + public static T WithLevel(this T builder, int level) + where T : IWithLevelBuilder + { + builder.Level = level; + return builder; + } + + public static T WithPath(this T builder, string path) + where T : IWithPathBuilder + { + builder.Path = path; + return builder; + } + + public static T WithSortOrder(this T builder, int sortOrder) + where T : IWithSortOrderBuilder + { + builder.SortOrder = sortOrder; + return builder; + } + + public static T WithDescription(this T builder, string description) + where T : IWithDescriptionBuilder + { + builder.Description = description; + return builder; + } + + public static T WithIcon(this T builder, string icon) + where T : IWithIconBuilder + { + builder.Icon = icon; + return builder; + } + + public static T WithThumbnail(this T builder, string thumbnail) + where T : IWithThumbnailBuilder + { + builder.Thumbnail = thumbnail; + return builder; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..b426cabaa6 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Extensions/StringExtensions.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Tests.Common.Builders.Extensions +{ + public static class StringExtensions + { + public static string ToCamelCase(this string s) + { + if (string.IsNullOrWhiteSpace(s)) + { + return string.Empty; + } + + if (s.Length == 1) + { + return s.ToLowerInvariant(); + } + + return char.ToLowerInvariant(s[0]) + s.Substring(1); + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs new file mode 100644 index 0000000000..c7e176e9b0 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/GenericCollectionBuilder.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Umbraco.Tests.Common.Builders +{ + public class GenericCollectionBuilder + : ChildBuilderBase> + { + private readonly IList _collection; + + public GenericCollectionBuilder(TBuilder parentBuilder) : base(parentBuilder) + { + _collection = new List(); + } + + public override IEnumerable Build() + { + return _collection; + } + + public GenericCollectionBuilder WithValue(T value) + { + _collection.Add(value); + return this; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs index 0cf2284a49..8f6aedcf43 100644 --- a/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/GenericDictionaryBuilder.cs @@ -14,10 +14,10 @@ namespace Umbraco.Tests.Common.Builders public override IDictionary Build() { - return new Dictionary(); + return _dictionary; } - public GenericDictionaryBuilder AddKeyValue(TKey key, TValue value) + public GenericDictionaryBuilder WithKeyValue(TKey key, TValue value) { _dictionary.Add(key, value); return this; diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs new file mode 100644 index 0000000000..1a155073b3 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDescriptionBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithDescriptionBuilder + { + string Description { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs new file mode 100644 index 0000000000..5de5224e18 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithIconBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithIconBuilder + { + string Icon { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs new file mode 100644 index 0000000000..dc6ee239ab --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithLevelBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithLevelBuilder + { + int? Level { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs new file mode 100644 index 0000000000..33d13b7ef1 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithParentIdBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithParentIdBuilder + { + int? ParentId { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs new file mode 100644 index 0000000000..ed632c4e7d --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithPathBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithPathBuilder + { + string Path { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs new file mode 100644 index 0000000000..3202c243fb --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithSortOrderBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithSortOrderBuilder + { + int? SortOrder { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs new file mode 100644 index 0000000000..ce5b10e274 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithThumbnailBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithThumbnailBuilder + { + string Thumbnail { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs new file mode 100644 index 0000000000..119e6a6e52 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithTrashedBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithTrashedBuilder + { + bool? Trashed { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs new file mode 100644 index 0000000000..cef4a35524 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberBuilder.cs @@ -0,0 +1,280 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreatorIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithNameBuilder, + IWithTrashedBuilder, + IWithLevelBuilder, + IWithPathBuilder, + IWithSortOrderBuilder + { + private MemberTypeBuilder _memberTypeBuilder; + private GenericCollectionBuilder _memberGroupsBuilder; + private GenericDictionaryBuilder _additionalDataBuilder; + private GenericDictionaryBuilder _propertyDataBuilder; + + private int? _id; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private string _name; + private int? _creatorId; + private string _username; + private string _rawPasswordValue; + private string _email; + private int? _failedPasswordAttempts; + private int? _level; + private string _path; + private bool? _isApproved; + private bool? _isLockedOut; + private DateTime? _lastLockoutDate; + private DateTime? _lastLoginDate; + private DateTime? _lastPasswordChangeDate; + private int? _sortOrder; + private bool? _trashed; + private int? _propertyIdsIncrementingFrom; + + public MemberBuilder WithUserName(string username) + { + _username = username; + return this; + } + + public MemberBuilder WithEmail(string email) + { + _email = email; + return this; + } + + public MemberBuilder WithRawPasswordValue(string rawPasswordValue) + { + _rawPasswordValue = rawPasswordValue; + return this; + } + + public MemberBuilder WithFailedPasswordAttempts(int failedPasswordAttempts) + { + _failedPasswordAttempts = failedPasswordAttempts; + return this; + } + + public MemberBuilder WithIsApproved(bool isApproved) + { + _isApproved = isApproved; + return this; + } + + public MemberBuilder WithIsLockedOut(bool isLockedOut) + { + _isLockedOut = isLockedOut; + return this; + } + + public MemberBuilder WithLastLockoutDate(DateTime lastLockoutDate) + { + _lastLockoutDate = lastLockoutDate; + return this; + } + + public MemberBuilder WithLastLoginDate(DateTime lastLoginDate) + { + _lastLoginDate = lastLoginDate; + return this; + } + + public MemberBuilder WithLastPasswordChangeDate(DateTime lastPasswordChangeDate) + { + _lastPasswordChangeDate = lastPasswordChangeDate; + return this; + } + + public MemberBuilder WithPropertyIdsIncrementingFrom(int propertyIdsIncrementingFrom) + { + _propertyIdsIncrementingFrom = propertyIdsIncrementingFrom; + return this; + } + + public MemberTypeBuilder AddMemberType() + { + var builder = new MemberTypeBuilder(this); + _memberTypeBuilder = builder; + return builder; + } + + public GenericCollectionBuilder AddMemberGroups() + { + var builder = new GenericCollectionBuilder(this); + _memberGroupsBuilder = builder; + return builder; + } + + public GenericDictionaryBuilder AddAdditionalData() + { + var builder = new GenericDictionaryBuilder(this); + _additionalDataBuilder = builder; + return builder; + } + + public GenericDictionaryBuilder AddPropertyData() + { + var builder = new GenericDictionaryBuilder(this); + _propertyDataBuilder = builder; + return builder; + } + + public override Member Build() + { + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var name = _name ?? Guid.NewGuid().ToString(); + var creatorId = _creatorId ?? 1; + var username = _username ?? string.Empty; + var email = _email ?? string.Empty; + var rawPasswordValue = _rawPasswordValue ?? string.Empty; + var failedPasswordAttempts = _failedPasswordAttempts ?? 0; + var level = _level ?? 1; + var path = _path ?? "-1"; + var isApproved = _isApproved ?? false; + var isLockedOut = _isLockedOut ?? false; + var lastLockoutDate = _lastLockoutDate ?? DateTime.Now; + var lastLoginDate = _lastLoginDate ?? DateTime.Now; + var lastPasswordChangeDate = _lastPasswordChangeDate ?? DateTime.Now; + var sortOrder = _sortOrder ?? 0; + var trashed = _trashed ?? false; + + if (_memberTypeBuilder == null) + { + throw new InvalidOperationException("A member cannot be constructed without providing a member type (use AddMemberType)."); + } + + var memberType = _memberTypeBuilder.Build(); + + var member = new Member(name, email, username, rawPasswordValue, memberType) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + CreatorId = creatorId, + Level = level, + Path = path, + SortOrder = sortOrder, + Trashed = trashed, + }; + + if (_propertyIdsIncrementingFrom.HasValue) + { + var i = _propertyIdsIncrementingFrom.Value; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + } + + member.FailedPasswordAttempts = failedPasswordAttempts; + member.IsApproved = isApproved; + member.IsLockedOut = isLockedOut; + member.LastLockoutDate = lastLockoutDate; + member.LastLoginDate = lastLoginDate; + member.LastPasswordChangeDate = lastPasswordChangeDate; + + if (_memberGroupsBuilder != null) + { + member.Groups = _memberGroupsBuilder.Build(); + } + + if (_additionalDataBuilder != null) + { + var additionalData = _additionalDataBuilder.Build(); + foreach (var kvp in additionalData) + { + member.AdditionalData.Add(kvp.Key, kvp.Value); + } + } + + if (_propertyDataBuilder != null) + { + var propertyData = _propertyDataBuilder.Build(); + foreach (var kvp in propertyData) + { + member.SetValue(kvp.Key, kvp.Value); + } + + member.ResetDirtyProperties(false); + } + + return member; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + + int? IWithLevelBuilder.Level + { + get => _level; + set => _level = value; + } + + string IWithPathBuilder.Path + { + get => _path; + set => _path = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs index 6092c79abb..bfd7f30a14 100644 --- a/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/MemberGroupBuilder.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders.Interfaces; @@ -14,15 +13,14 @@ namespace Umbraco.Tests.Common.Builders IWithUpdateDateBuilder, IWithNameBuilder { + private GenericDictionaryBuilder _additionalDataBuilder; + private int? _id; private Guid? _key; private DateTime? _createDate; private DateTime? _updateDate; private string _name; private int? _creatorId; - private IDictionary _additionalData = new Dictionary(); - - private GenericDictionaryBuilder _additionalDataBuilder; public GenericDictionaryBuilder AddAdditionalData() { @@ -40,7 +38,7 @@ namespace Umbraco.Tests.Common.Builders var name = _name ?? Guid.NewGuid().ToString(); var creatorId = _creatorId ?? 1; - return new MemberGroup + var memberGroup = new MemberGroup { Id = id, Key = key, @@ -49,6 +47,17 @@ namespace Umbraco.Tests.Common.Builders Name = name, CreatorId = creatorId, }; + + if (_additionalDataBuilder != null) + { + var additionalData = _additionalDataBuilder.Build(); + foreach (var kvp in additionalData) + { + memberGroup.AdditionalData.Add(kvp.Key, kvp.Value); + } + } + + return memberGroup; } int? IWithIdBuilder.Id diff --git a/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs new file mode 100644 index 0000000000..b01b8a1680 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/MemberTypeBuilder.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class MemberTypeBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithAliasBuilder, + IWithNameBuilder, + IWithParentIdBuilder, + IWithSortOrderBuilder, + IWithCreatorIdBuilder, + IWithDescriptionBuilder, + IWithIconBuilder, + IWithThumbnailBuilder, + IWithTrashedBuilder + { + private readonly List _propertyGroupBuilders = new List(); + + private int? _id; + private string _alias; + private string _name; + private int? _parentId; + private int? _sortOrder; + private int? _creatorId; + private string _description; + private string _icon; + private string _thumbnail; + private bool? _trashed; + + public MemberTypeBuilder(MemberBuilder parentBuilder) : base(parentBuilder) + { + } + + public MemberTypeBuilder WithMembershipPropertyGroup() + { + var builder = new PropertyGroupBuilder(this) + .WithName(Constants.Conventions.Member.StandardPropertiesGroupName) + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextArea) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias(Constants.Conventions.Member.Comments) + .WithName(Constants.Conventions.Member.CommentsLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.IsApproved) + .WithName(Constants.Conventions.Member.IsApprovedLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Boolean) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.IsLockedOut) + .WithName(Constants.Conventions.Member.IsLockedOutLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastLoginDate) + .WithName(Constants.Conventions.Member.LastLoginDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastPasswordChangeDate) + .WithName(Constants.Conventions.Member.LastPasswordChangeDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Date) + .WithAlias(Constants.Conventions.Member.LastLockoutDate) + .WithName(Constants.Conventions.Member.LastLockoutDateLabel) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.Label) + .WithValueStorageType(ValueStorageType.Integer) + .WithAlias(Constants.Conventions.Member.FailedPasswordAttempts) + .WithName(Constants.Conventions.Member.FailedPasswordAttemptsLabel) + .Done(); + _propertyGroupBuilders.Add(builder); + return this; + } + + public PropertyGroupBuilder AddPropertyGroup() + { + var builder = new PropertyGroupBuilder(this); + _propertyGroupBuilders.Add(builder); + return builder; + } + + public override MemberType Build() + { + var id = _id ?? 1; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var parentId = _parentId ?? -1; + var sortOrder = _sortOrder ?? 0; + var description = _description ?? string.Empty; + var icon = _icon ?? string.Empty; + var thumbnail = _thumbnail ?? string.Empty; + var creatorId = _creatorId ?? 0; + var trashed = _trashed ?? false; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + var memberType = new MemberType(shortStringHelper, parentId) + { + Id = id, + Alias = alias, + Name = name, + SortOrder = sortOrder, + Description = description, + Icon = icon, + Thumbnail = thumbnail, + CreatorId = creatorId, + Trashed = trashed, + }; + + foreach (var propertyGroup in _propertyGroupBuilders.Select(x => x.Build())) + { + memberType.PropertyGroups.Add(propertyGroup); + } + + memberType.ResetDirtyProperties(false); + + return memberType; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithParentIdBuilder.ParentId + { + get => _parentId; + set => _parentId = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + + int? IWithCreatorIdBuilder.CreatorId + { + get => _creatorId; + set => _creatorId = value; + } + + string IWithDescriptionBuilder.Description + { + get => _description; + set => _description = value; + } + + string IWithIconBuilder.Icon + { + get => _icon; + set => _icon = value; + } + + string IWithThumbnailBuilder.Thumbnail + { + get => _thumbnail; + set => _thumbnail = value; + } + + bool? IWithTrashedBuilder.Trashed + { + get => _trashed; + set => _trashed = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs new file mode 100644 index 0000000000..4b349a7e8a --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyGroupBuilder + : ChildBuilderBase, // TODO: likely want to genericise this, so can use for document and media types too. + IWithNameBuilder, + IWithSortOrderBuilder + { + private readonly List _propertyTypeBuilders = new List(); + + private string _name; + private int? _sortOrder; + + public PropertyGroupBuilder(MemberTypeBuilder parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder AddPropertyType() + { + var builder = new PropertyTypeBuilder(this); + _propertyTypeBuilders.Add(builder); + return builder; + } + + public override PropertyGroup Build() + { + var name = _name ?? Guid.NewGuid().ToString(); + var sortOrder = _sortOrder ?? 0; + + var properties = new PropertyTypeCollection(false); + foreach (var propertyType in _propertyTypeBuilders.Select(x => x.Build())) + { + properties.Add(propertyType); + } + + return new PropertyGroup(properties) + { + Name = name, + SortOrder = sortOrder, + }; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs new file mode 100644 index 0000000000..955e2fca4c --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/PropertyTypeBuilder.cs @@ -0,0 +1,93 @@ +using System; +using Moq; +using Umbraco.Core.Models; +using Umbraco.Core.Strings; +using Umbraco.Tests.Common.Builders.Extensions; +using Umbraco.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Tests.Common.Builders +{ + public class PropertyTypeBuilder + : ChildBuilderBase, + IWithAliasBuilder, + IWithNameBuilder, + IWithSortOrderBuilder, + IWithDescriptionBuilder + { + private string _propertyEditorAlias; + private ValueStorageType? _valueStorageType; + private string _alias; + private string _name; + private int? _sortOrder; + private string _description; + private int? _dataTypeId; + + public PropertyTypeBuilder(PropertyGroupBuilder parentBuilder) : base(parentBuilder) + { + } + + public PropertyTypeBuilder WithPropertyEditorAlias(string propertyEditorAlias) + { + _propertyEditorAlias = propertyEditorAlias; + return this; + } + + public PropertyTypeBuilder WithValueStorageType(ValueStorageType valueStorageType) + { + _valueStorageType = valueStorageType; + return this; + } + + public PropertyTypeBuilder WithDataTypeId(int dataTypeId) + { + _dataTypeId = dataTypeId; + return this; + } + + public override PropertyType Build() + { + var propertyEditorAlias = _propertyEditorAlias ?? Guid.NewGuid().ToString().ToCamelCase(); + var valueStorageType = _valueStorageType ?? ValueStorageType.Ntext; + var name = _name ?? Guid.NewGuid().ToString(); + var alias = _alias ?? name.ToCamelCase(); + var sortOrder = _sortOrder ?? 0; + var dataTypeId = _dataTypeId ?? 0; + var description = _description ?? string.Empty; + + var shortStringHelper = new DefaultShortStringHelper(new DefaultShortStringHelperConfig()); + + return new PropertyType(shortStringHelper, propertyEditorAlias, valueStorageType) + { + Alias = alias, + Name = name, + SortOrder = sortOrder, + DataTypeId = dataTypeId, + Description = description, + }; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + int? IWithSortOrderBuilder.SortOrder + { + get => _sortOrder; + set => _sortOrder = value; + } + + string IWithDescriptionBuilder.Description + { + get => _description; + set => _description = value; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index 158a70598c..f2b17cc23f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -59,8 +59,8 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models .WithCreateDate(DateTime.Now) .WithUpdateDate(DateTime.Now) .AddAdditionalData() - .AddKeyValue("test1", 123) - .AddKeyValue("test2", "hello") + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs new file mode 100644 index 0000000000..cda7641e8e --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -0,0 +1,154 @@ +using System; +using System.Diagnostics; +using System.Linq; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class MemberTests + { + private readonly MemberBuilder _builder = new MemberBuilder(); + + [Test] + public void Can_Deep_Clone() + { + // Arrange + var member = BuildMember(); + + // Act + var clone = (Member)member.DeepClone(); + + // Assert + Assert.AreNotSame(clone, member); + Assert.AreEqual(clone, member); + Assert.AreEqual(clone.Id, member.Id); + Assert.AreEqual(clone.VersionId, member.VersionId); + Assert.AreEqual(clone.AdditionalData, member.AdditionalData); + Assert.AreEqual(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); + Assert.AreEqual(clone.CreateDate, member.CreateDate); + Assert.AreEqual(clone.CreatorId, member.CreatorId); + Assert.AreEqual(clone.Comments, member.Comments); + Assert.AreEqual(clone.Key, member.Key); + Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); + Assert.AreEqual(clone.Level, member.Level); + Assert.AreEqual(clone.Path, member.Path); + Assert.AreEqual(clone.Groups, member.Groups); + Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); + Assert.AreEqual(clone.IsApproved, member.IsApproved); + Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); + Assert.AreEqual(clone.SortOrder, member.SortOrder); + Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); + Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); + Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); + Assert.AreEqual(clone.Trashed, member.Trashed); + Assert.AreEqual(clone.UpdateDate, member.UpdateDate); + Assert.AreEqual(clone.VersionId, member.VersionId); + Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); + Assert.AreNotSame(clone.Properties, member.Properties); + Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); + for (var index = 0; index < member.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], member.Properties[index]); + Assert.AreEqual(clone.Properties[index], member.Properties[index]); + } + + // this can be the same, it is immutable + Assert.AreSame(clone.ContentType, member.ContentType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); + } + + [Test] + public void Can_Serialize_Without_Error() + { + var member = BuildMember(); + + var json = JsonConvert.SerializeObject(member); + Debug.Print(json); + } + + private Member BuildMember() + { + return _builder + .AddMemberType() + .WithId(99) + .WithAlias("memberType") + .WithName("Member Type") + .WithMembershipPropertyGroup() + .AddPropertyGroup() + .WithName("Content") + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias("title") + .WithName("Title") + .WithSortOrder(1) + .WithDataTypeId(-88) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias("bodyText") + .WithName("Body Text") + .WithSortOrder(2) + .WithDataTypeId(-87) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias("author") + .WithName("Author") + .WithDescription("Name of the author") + .WithSortOrder(3) + .WithDataTypeId(-88) + .Done() + .Done() + .Done() + .WithId(10) + .WithKey(Guid.NewGuid()) + .WithName("Name") + .WithUserName("user") + .WithRawPasswordValue("raw pass") + .WithEmail("email@email.com") + .WithCreatorId(22) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) + .WithFailedPasswordAttempts(22) + .WithLevel(3) + .WithPath("-1, 4, 10") + .WithIsApproved(true) + .WithIsLockedOut(true) + .WithLastLockoutDate(DateTime.Now) + .WithLastLoginDate(DateTime.Now) + .WithLastPasswordChangeDate(DateTime.Now) + .WithSortOrder(5) + .WithTrashed(false) + .AddMemberGroups() + .WithValue("group1") + .WithValue("group2") + .Done() + .AddAdditionalData() + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") + .Done() + .WithPropertyIdsIncrementingFrom(200) + .AddPropertyData() + .WithKeyValue("title", "Name member") + .WithKeyValue("bodyText", "This is a subpage") + .WithKeyValue("author", "John Doe") + .Done() + .Build(); + } + } +} diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs deleted file mode 100644 index e0cd826536..0000000000 --- a/src/Umbraco.Tests/Models/MemberTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Newtonsoft.Json; -using NUnit.Framework; -using Umbraco.Core.Composing; -using Umbraco.Core.Models; -using Umbraco.Tests.TestHelpers; -using Umbraco.Tests.TestHelpers.Entities; - -namespace Umbraco.Tests.Models -{ - [TestFixture] - public class MemberTests - { - [Test] - public void Can_Deep_Clone() - { - // Arrange - var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); - memberType.Id = 99; - var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); - var i = 200; - foreach (var property in member.Properties) - { - property.Id = ++i; - } - member.Id = 10; - member.CreateDate = DateTime.Now; - member.CreatorId = 22; - member.Comments = "comments"; - member.Key = Guid.NewGuid(); - member.FailedPasswordAttempts = 22; - member.Level = 3; - member.Path = "-1,4,10"; - member.Groups = new[] {"group1", "group2"}; - member.IsApproved = true; - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - member.RawPasswordValue = "raw pass"; - member.SortOrder = 5; - member.Trashed = false; - member.UpdateDate = DateTime.Now; - member.AdditionalData.Add("test1", 123); - member.AdditionalData.Add("test2", "hello"); - - // Act - var clone = (Member)member.DeepClone(); - - // Assert - Assert.AreNotSame(clone, member); - Assert.AreEqual(clone, member); - Assert.AreEqual(clone.Id, member.Id); - Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.AdditionalData, member.AdditionalData); - Assert.AreEqual(clone.ContentType, member.ContentType); - Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); - Assert.AreEqual(clone.CreateDate, member.CreateDate); - Assert.AreEqual(clone.CreatorId, member.CreatorId); - Assert.AreEqual(clone.Comments, member.Comments); - Assert.AreEqual(clone.Key, member.Key); - Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); - Assert.AreEqual(clone.Level, member.Level); - Assert.AreEqual(clone.Path, member.Path); - Assert.AreEqual(clone.Groups, member.Groups); - Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); - Assert.AreEqual(clone.IsApproved, member.IsApproved); - Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); - Assert.AreEqual(clone.SortOrder, member.SortOrder); - Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); - Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); - Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); - Assert.AreEqual(clone.Trashed, member.Trashed); - Assert.AreEqual(clone.UpdateDate, member.UpdateDate); - Assert.AreEqual(clone.VersionId, member.VersionId); - Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); - Assert.AreNotSame(clone.Properties, member.Properties); - Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); - for (var index = 0; index < member.Properties.Count; index++) - { - Assert.AreNotSame(clone.Properties[index], member.Properties[index]); - Assert.AreEqual(clone.Properties[index], member.Properties[index]); - } - - // this can be the same, it is immutable - Assert.AreSame(clone.ContentType, member.ContentType); - - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); - } - } - - [Test] - public void Can_Serialize_Without_Error() - { - var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); - memberType.Id = 99; - var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); - var i = 200; - foreach (var property in member.Properties) - { - property.Id = ++i; - } - member.Id = 10; - member.CreateDate = DateTime.Now; - member.CreatorId = 22; - member.Comments = "comments"; - member.Key = Guid.NewGuid(); - member.FailedPasswordAttempts = 22; - member.Level = 3; - member.Path = "-1,4,10"; - member.Groups = new[] { "group1", "group2" }; - member.IsApproved = true; - member.IsLockedOut = true; - member.LastLockoutDate = DateTime.Now; - member.LastLoginDate = DateTime.Now; - member.LastPasswordChangeDate = DateTime.Now; - member.RawPasswordValue = "raw pass"; - member.SortOrder = 5; - member.Trashed = false; - member.UpdateDate = DateTime.Now; - member.AdditionalData.Add("test1", 123); - member.AdditionalData.Add("test2", "hello"); - - var json = JsonConvert.SerializeObject(member); - Debug.Print(json); - } - } -} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index eb500bf7e3..79bd8cfc3d 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -298,7 +298,6 @@ - From f6af286e0bc312243495636974f59788ed268d34 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 30 Mar 2020 07:42:02 +0200 Subject: [PATCH 35/42] Added tests confirming correct building of unit test objects. --- .../Models/DataTypeTests.cs | 18 ++- .../Models/MemberGroupTests.cs | 45 +++++- .../Models/MemberTests.cs | 144 +++++++++++++----- 3 files changed, 155 insertions(+), 52 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index 4be32a9b4f..53431bfdea 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -4,7 +4,6 @@ using Umbraco.Core.Models; using Umbraco.Tests.Common.Builders; using Umbraco.Tests.Common.Builders.Extensions; - namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { [TestFixture] @@ -12,11 +11,26 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly DataTypeBuilder _builder = new DataTypeBuilder(); + private const int _testId = 3123; + + [Test] + public void Is_Built_Correctly() + { + // Arrange + // Act + var dtd = _builder + .WithId(_testId) + .Build(); + + // Assert + Assert.AreEqual(_testId, dtd.Id); + } + [Test] public void Can_Deep_Clone() { var dtd = _builder - .WithId(3123) + .WithId(_testId) .Build(); var clone = (DataType) dtd.DeepClone(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index f2b17cc23f..a6718660dd 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; @@ -13,6 +14,34 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); + private const int _testId = 6; + private const string _testName = "Test Group"; + private const int _testCreatorId = 4; + private readonly Guid _testKey = Guid.NewGuid(); + private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); + private readonly DateTime _testUpdateDate = DateTime.Now; + private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); + private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); + + [Test] + public void Is_Built_Correctly() + { + // Arrange + // Act + var group = BuildMemberGroup(); + + // Assert + Assert.AreEqual(_testId, group.Id); + Assert.AreEqual(_testKey, group.Key); + Assert.AreEqual(_testName, group.Name); + Assert.AreEqual(_testCreateDate, group.CreateDate); + Assert.AreEqual(_testUpdateDate, group.UpdateDate); + Assert.AreEqual(_testCreatorId, group.CreatorId); + Assert.AreEqual(2, group.AdditionalData.Count); + Assert.AreEqual(_testAdditionalData1.Value, group.AdditionalData[_testAdditionalData1.Key]); + Assert.AreEqual(_testAdditionalData2.Value, group.AdditionalData[_testAdditionalData2.Key]); + } + [Test] public void Can_Deep_Clone() { @@ -52,15 +81,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models private MemberGroup BuildMemberGroup() { return _builder - .WithId(6) - .WithKey(Guid.NewGuid()) - .WithName("asdf") - .WithCreatorId(4) - .WithCreateDate(DateTime.Now) - .WithUpdateDate(DateTime.Now) + .WithId(_testId) + .WithKey(_testKey) + .WithName(_testName) + .WithCreatorId(_testCreatorId) + .WithCreateDate(_testCreateDate) + .WithUpdateDate(_testUpdateDate) .AddAdditionalData() - .WithKeyValue("test1", 123) - .WithKeyValue("test2", "hello") + .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) + .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs index cda7641e8e..9420d03c8f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Newtonsoft.Json; @@ -15,6 +16,65 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberBuilder _builder = new MemberBuilder(); + private const int _testMemberTypeId = 99; + private const string _testMemberTypeAlias = "memberType"; + private const string _testMemberTypeName = "Member Type"; + private const string _testMemberTypePropertyGroupName = "Content"; + private const int _testId = 6; + private const string _testName = "Fred"; + private const string _testUsername = "fred"; + private const string _testRawPasswordValue = "raw pass"; + private const string _testEmail = "email@email.com"; + private const int _testCreatorId = 22; + private const int _testLevel = 3; + private const string _testPath = "-1, 4, 10"; + private const bool _testIsApproved = true; + private const bool _testIsLockedOut = true; + private const int _testSortOrder = 5; + private const bool _testTrashed = false; + private readonly Guid _testKey = Guid.NewGuid(); + private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); + private readonly DateTime _testUpdateDate = DateTime.Now; + private readonly DateTime _testLastLockoutDate = DateTime.Now.AddHours(-2); + private readonly DateTime _testLastLoginDate = DateTime.Now.AddHours(-3); + private readonly DateTime _testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType1 = ("title", "Title", string.Empty, 1, -88); + private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType2 = ("bodyText", "Body Text", string.Empty, 2, -87); + private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType3 = ("author", "Author", "Writer of the article", 1, -88); + private readonly string[] _testGroups = new string[] { "group1", "group2" }; + private readonly KeyValuePair _testPropertyData1 = new KeyValuePair("title", "Name member"); + private readonly KeyValuePair _testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); + private readonly KeyValuePair _testPropertyData3 = new KeyValuePair("author", "John Doe"); + private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); + private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); + + [Test] + public void Is_Built_Correctly() + { + // Arrange + // Act + var member = BuildMember(); + + // Assert + Assert.AreEqual(_testMemberTypeId, member.ContentTypeId); + Assert.AreEqual(_testMemberTypeAlias, member.ContentType.Alias); + Assert.AreEqual(_testMemberTypeName, member.ContentType.Name); + Assert.AreEqual(_testId, member.Id); + Assert.AreEqual(_testKey, member.Key); + Assert.AreEqual(_testName, member.Name); + Assert.AreEqual(_testCreateDate, member.CreateDate); + Assert.AreEqual(_testUpdateDate, member.UpdateDate); + Assert.AreEqual(_testCreatorId, member.CreatorId); + Assert.AreEqual(_testGroups, member.Groups.ToArray()); + Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom + Assert.AreEqual(_testPropertyData1.Value, member.GetValue(_testPropertyData1.Key)); + Assert.AreEqual(_testPropertyData2.Value, member.GetValue(_testPropertyData2.Key)); + Assert.AreEqual(_testPropertyData3.Value, member.GetValue(_testPropertyData3.Key)); + Assert.AreEqual(2, member.AdditionalData.Count); + Assert.AreEqual(_testAdditionalData1.Value, member.AdditionalData[_testAdditionalData1.Key]); + Assert.AreEqual(_testAdditionalData2.Value, member.AdditionalData[_testAdditionalData2.Key]); + } + [Test] public void Can_Deep_Clone() { @@ -81,72 +141,72 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { return _builder .AddMemberType() - .WithId(99) - .WithAlias("memberType") - .WithName("Member Type") + .WithId(_testMemberTypeId) + .WithAlias(_testMemberTypeAlias) + .WithName(_testMemberTypeName) .WithMembershipPropertyGroup() .AddPropertyGroup() - .WithName("Content") + .WithName(_testMemberTypePropertyGroupName) .WithSortOrder(1) .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias("title") - .WithName("Title") - .WithSortOrder(1) - .WithDataTypeId(-88) + .WithAlias(_testPropertyType1.alias) + .WithName(_testPropertyType1.name) + .WithSortOrder(_testPropertyType1.sortOrder) + .WithDataTypeId(_testPropertyType1.dataTypeId) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Ntext) - .WithAlias("bodyText") - .WithName("Body Text") - .WithSortOrder(2) - .WithDataTypeId(-87) + .WithAlias(_testPropertyType2.alias) + .WithName(_testPropertyType2.name) + .WithSortOrder(_testPropertyType2.sortOrder) + .WithDataTypeId(_testPropertyType2.dataTypeId) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias("author") - .WithName("Author") - .WithDescription("Name of the author") - .WithSortOrder(3) - .WithDataTypeId(-88) + .WithAlias(_testPropertyType3.alias) + .WithName(_testPropertyType3.name) + .WithDescription(_testPropertyType3.description) + .WithSortOrder(_testPropertyType3.sortOrder) + .WithDataTypeId(_testPropertyType3.dataTypeId) .Done() .Done() .Done() - .WithId(10) - .WithKey(Guid.NewGuid()) - .WithName("Name") - .WithUserName("user") - .WithRawPasswordValue("raw pass") - .WithEmail("email@email.com") - .WithCreatorId(22) - .WithCreateDate(DateTime.Now) - .WithUpdateDate(DateTime.Now) + .WithId(_testId) + .WithKey(_testKey) + .WithName(_testName) + .WithUserName(_testUsername) + .WithRawPasswordValue(_testRawPasswordValue) + .WithEmail(_testEmail) + .WithCreatorId(_testCreatorId) + .WithCreateDate(_testCreateDate) + .WithUpdateDate(_testUpdateDate) .WithFailedPasswordAttempts(22) - .WithLevel(3) - .WithPath("-1, 4, 10") - .WithIsApproved(true) - .WithIsLockedOut(true) - .WithLastLockoutDate(DateTime.Now) - .WithLastLoginDate(DateTime.Now) - .WithLastPasswordChangeDate(DateTime.Now) - .WithSortOrder(5) - .WithTrashed(false) + .WithLevel(_testLevel) + .WithPath(_testPath) + .WithIsApproved(_testIsApproved) + .WithIsLockedOut(_testIsLockedOut) + .WithLastLockoutDate(_testLastLockoutDate) + .WithLastLoginDate(_testLastLoginDate) + .WithLastPasswordChangeDate(_testLastPasswordChangeDate) + .WithSortOrder(_testSortOrder) + .WithTrashed(_testTrashed) .AddMemberGroups() - .WithValue("group1") - .WithValue("group2") + .WithValue(_testGroups[0]) + .WithValue(_testGroups[1]) .Done() .AddAdditionalData() - .WithKeyValue("test1", 123) - .WithKeyValue("test2", "hello") + .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) + .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) .Done() .WithPropertyIdsIncrementingFrom(200) .AddPropertyData() - .WithKeyValue("title", "Name member") - .WithKeyValue("bodyText", "This is a subpage") - .WithKeyValue("author", "John Doe") + .WithKeyValue(_testPropertyData1.Key, _testPropertyData1.Value) + .WithKeyValue(_testPropertyData2.Key, _testPropertyData2.Value) + .WithKeyValue(_testPropertyData3.Key, _testPropertyData3.Value) .Done() .Build(); } From 5fad35d0eedbfae2a09fc29045f88ea321dc787a Mon Sep 17 00:00:00 2001 From: Olivier Bossaer Date: Sat, 28 Mar 2020 14:37:31 +0100 Subject: [PATCH 36/42] Fix #7521 - Runtime error if removing email notificationMethod --- .../HealthCheck/NotificationMethods/EmailNotificationMethod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs index 87c0e4f46d..873b356214 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/EmailNotificationMethod.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.HealthCheck.NotificationMethods public EmailNotificationMethod(ILocalizedTextService textService, IRuntimeState runtimeState, ILogger logger) { - var recipientEmail = Settings["recipientEmail"]?.Value; + var recipientEmail = Settings?["recipientEmail"]?.Value; if (string.IsNullOrWhiteSpace(recipientEmail)) { Enabled = false; From 8cba80913a46628778b477b39d1e87bb4b5e1fca Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 30 Mar 2020 13:36:41 +0200 Subject: [PATCH 37/42] Remove generated files from git and ignore them --- .gitignore | 3 + .../Umbraco/js/main.controller.js | 220 ------- .../Umbraco/js/navigation.controller.js | 544 ------------------ 3 files changed, 3 insertions(+), 764 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/js/main.controller.js delete mode 100644 src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js diff --git a/.gitignore b/.gitignore index 12ad3299ad..870d24c648 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,9 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/navigation.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.controller.js +src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/utilities.js src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/ src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js diff --git a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js b/src/Umbraco.Web.UI/Umbraco/js/main.controller.js deleted file mode 100644 index 883907d1dc..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/main.controller.js +++ /dev/null @@ -1,220 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.MainController - * @function - * - * @description - * The main application controller - * - */ -function MainController($scope, $location, appState, treeService, notificationsService, - userService, historyService, updateChecker, navigationService, eventsService, - tmhDynamicLocale, localStorageService, editorService, overlayService, assetsService, tinyMceAssets) { - - //the null is important because we do an explicit bool check on this in the view - $scope.authenticated = null; - $scope.touchDevice = appState.getGlobalState("touchDevice"); - $scope.infiniteMode = false; - $scope.overlay = {}; - $scope.drawer = {}; - $scope.search = {}; - $scope.login = {}; - $scope.tabbingActive = false; - - // Load TinyMCE assets ahead of time in the background for the user - // To help with first load of the RTE - tinyMceAssets.forEach(function (tinyJsAsset) { - assetsService.loadJs(tinyJsAsset, $scope); - }); - - // There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution. - // For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2 - function handleFirstTab(evt) { - if (evt.keyCode === 9) { - $scope.tabbingActive = true; - $scope.$digest(); - window.removeEventListener('keydown', handleFirstTab); - window.addEventListener('mousedown', disableTabbingActive); - } - } - - function disableTabbingActive(evt) { - $scope.tabbingActive = false; - $scope.$digest(); - window.removeEventListener('mousedown', disableTabbingActive); - window.addEventListener("keydown", handleFirstTab); - } - - window.addEventListener("keydown", handleFirstTab); - - - $scope.removeNotification = function (index) { - notificationsService.remove(index); - }; - - $scope.closeSearch = function() { - appState.setSearchState("show", false); - }; - - $scope.showLoginScreen = function(isTimedOut) { - $scope.login.isTimedOut = isTimedOut; - $scope.login.show = true; - }; - - $scope.hideLoginScreen = function() { - $scope.login.show = false; - }; - - var evts = []; - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function (evt, data) { - $scope.authenticated = null; - $scope.user = null; - const isTimedOut = data && data.isTimedOut ? true : false; - $scope.showLoginScreen(isTimedOut); - - // Remove the localstorage items for tours shown - // Means that when next logged in they can be re-shown if not already dismissed etc - localStorageService.remove("emailMarketingTourShown"); - localStorageService.remove("introTourShown"); - })); - - evts.push(eventsService.on("app.userRefresh", function(evt) { - userService.refreshCurrentUser().then(function(data) { - $scope.user = data; - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - }); - })); - - //when the app is ready/user is logged in, setup the data - evts.push(eventsService.on("app.ready", function (evt, data) { - - $scope.authenticated = data.authenticated; - $scope.user = data.user; - - updateChecker.check().then(function (update) { - if (update && update !== "null") { - if (update.type !== "None") { - var notification = { - headline: "Update available", - message: "Click to download", - sticky: true, - type: "info", - url: update.url - }; - notificationsService.add(notification); - } - } - }); - - //if the user has changed we need to redirect to the root so they don't try to continue editing the - //last item in the URL (NOTE: the user id can equal zero, so we cannot just do !data.lastUserId since that will resolve to true) - if (data.lastUserId !== undefined && data.lastUserId !== null && data.lastUserId !== data.user.id) { - - var section = appState.getSectionState("currentSection"); - if (section) { - //if there's a section already assigned, reload it so the tree is cleared - navigationService.reloadSection(section); - } - - $location.path("/").search(""); - historyService.removeAll(); - treeService.clearCache(); - editorService.closeAll(); - overlayService.close(); - - //if the user changed, clearout local storage too - could contain sensitive data - localStorageService.clearAll(); - } - - //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data - if (data.loginType === "credentials") { - localStorageService.clearAll(); - } - - //Load locale file - if ($scope.user.locale) { - tmhDynamicLocale.set($scope.user.locale); - } - - })); - - // events for search - evts.push(eventsService.on("appState.searchState.changed", function (e, args) { - if (args.key === "show") { - $scope.search.show = args.value; - } - })); - - // events for drawer - // manage the help dialog by subscribing to the showHelp appState - evts.push(eventsService.on("appState.drawerState.changed", function (e, args) { - // set view - if (args.key === "view") { - $scope.drawer.view = args.value; - } - // set custom model - if (args.key === "model") { - $scope.drawer.model = args.value; - } - // show / hide drawer - if (args.key === "showDrawer") { - $scope.drawer.show = args.value; - } - })); - - // events for overlays - evts.push(eventsService.on("appState.overlay", function (name, args) { - $scope.overlay = args; - })); - - // events for tours - evts.push(eventsService.on("appState.tour.start", function (name, args) { - $scope.tour = args; - $scope.tour.show = true; - })); - - evts.push(eventsService.on("appState.tour.end", function () { - $scope.tour = null; - })); - - evts.push(eventsService.on("appState.tour.complete", function () { - $scope.tour = null; - })); - - // events for backdrop - evts.push(eventsService.on("appState.backdrop", function (name, args) { - $scope.backdrop = args; - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); - -} - - -//register it -angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular-i18n/angular-locale_{{locale}}.js'); - }); diff --git a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js b/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js deleted file mode 100644 index 194c45afe6..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/js/navigation.controller.js +++ /dev/null @@ -1,544 +0,0 @@ - -/** - * @ngdoc controller - * @name Umbraco.NavigationController - * @function - * - * @description - * Handles the section area of the app - * - * @param {navigationService} navigationService A reference to the navigationService - */ -function NavigationController($scope, $rootScope, $location, $log, $q, $routeParams, $timeout, $cookies, treeService, appState, navigationService, keyboardService, historyService, eventsService, angularHelper, languageResource, contentResource, editorState) { - - //this is used to trigger the tree to start loading once everything is ready - var treeInitPromise = $q.defer(); - - $scope.treeApi = {}; - - //Bind to the main tree events - $scope.onTreeInit = function () { - - $scope.treeApi.callbacks.treeNodeExpanded(nodeExpandedHandler); - - //when a tree is loaded into a section, we need to put it into appState - $scope.treeApi.callbacks.treeLoaded(function (args) { - appState.setTreeState("currentRootNode", args.tree); - }); - - //when a tree node is synced this event will fire, this allows us to set the currentNode - $scope.treeApi.callbacks.treeSynced(function (args) { - - if (args.activate === undefined || args.activate === true) { - //set the current selected node - appState.setTreeState("selectedNode", args.node); - //when a node is activated, this is the same as clicking it and we need to set the - //current menu item to be this node as well. - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - } - }); - - //this reacts to the options item in the tree - $scope.treeApi.callbacks.treeOptionsClick(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - //Set the current action node (this is not the same as the current selected node!) - //appState.setMenuState("currentNode", args.node);// Niels: No, we are setting it from the dialog. - - if (args.event && args.event.altKey) { - args.skipDefault = true; - } - - navigationService.showMenu(args); - }); - - $scope.treeApi.callbacks.treeNodeAltSelect(function (args) { - args.event.stopPropagation(); - args.event.preventDefault(); - - args.skipDefault = true; - navigationService.showMenu(args); - }); - - //this reacts to tree items themselves being clicked - //the tree directive should not contain any handling, simply just bubble events - $scope.treeApi.callbacks.treeNodeSelect(function (args) { - var n = args.node; - args.event.stopPropagation(); - args.event.preventDefault(); - - if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") { - //this is a legacy tree node! - var jsPrefix = "javascript:"; - var js; - if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) { - js = n.metaData["jsClickCallback"].substr(jsPrefix.length); - } - else { - js = n.metaData["jsClickCallback"]; - } - try { - var func = eval(js); - //this is normally not necessary since the eval above should execute the method and will return nothing. - if (func != null && (typeof func === "function")) { - func.call(); - } - } - catch (ex) { - $log.error("Error evaluating js callback from legacy tree node: " + ex); - } - } - else if (n.routePath) { - //add action to the history service - historyService.add({ name: n.name, link: n.routePath, icon: n.icon }); - - //put this node into the tree state - appState.setTreeState("selectedNode", args.node); - //when a node is clicked we also need to set the active menu node to this node - //appState.setMenuState("currentNode", args.node); - - //not legacy, lets just set the route value and clear the query string if there is one. - $location.path(n.routePath); - navigationService.clearSearch(); - } - else if (n.section) { - $location.path(n.section); - navigationService.clearSearch(); - } - - navigationService.hideNavigation(); - }); - - return treeInitPromise.promise; - } - - //set up our scope vars - $scope.showContextMenuDialog = false; - $scope.showContextMenu = false; - $scope.showSearchResults = false; - $scope.menuDialogTitle = null; - $scope.menuActions = []; - $scope.menuNode = null; - $scope.languages = []; - $scope.selectedLanguage = {}; - $scope.page = {}; - $scope.page.languageSelectorIsOpen = false; - - $scope.currentSection = null; - $scope.customTreeParams = null; - $scope.treeCacheKey = "_"; - $scope.showNavigation = appState.getGlobalState("showNavigation"); - // tracks all expanded paths so when the language is switched we can resync it with the already loaded paths - var expandedPaths = []; - - //trigger search with a hotkey: - keyboardService.bind("ctrl+shift+s", function () { - navigationService.showSearch(); - }); - - //// TODO: remove this it's not a thing - //$scope.selectedId = navigationService.currentId; - - var isInit = false; - var evts = []; - - //Listen for global state changes - evts.push(eventsService.on("appState.globalState.changed", function (e, args) { - if (args.key === "showNavigation") { - $scope.showNavigation = args.value; - } - })); - - //Listen for menu state changes - evts.push(eventsService.on("appState.menuState.changed", function (e, args) { - if (args.key === "showMenuDialog") { - $scope.showContextMenuDialog = args.value; - } - if (args.key === "dialogTemplateUrl") { - $scope.dialogTemplateUrl = args.value; - } - if (args.key === "showMenu") { - $scope.showContextMenu = args.value; - } - if (args.key === "dialogTitle") { - $scope.menuDialogTitle = args.value; - } - if (args.key === "menuActions") { - $scope.menuActions = args.value; - } - if (args.key === "currentNode") { - $scope.menuNode = args.value; - } - })); - - //Listen for tree state changes - evts.push(eventsService.on("appState.treeState.changed", function (e, args) { - if (args.key === "currentRootNode") { - - //if the changed state is the currentRootNode, determine if this is a full screen app - if (args.value.root && args.value.root.containsTrees === false) { - $rootScope.emptySection = true; - } - else { - $rootScope.emptySection = false; - } - } - - })); - - //Listen for section state changes - evts.push(eventsService.on("appState.sectionState.changed", function (e, args) { - - //section changed - if (args.key === "currentSection" && $scope.currentSection != args.value) { - //before loading the main tree we need to ensure that the nav is ready - navigationService.waitForNavReady().then(() => { - $scope.currentSection = args.value; - //load the tree - configureTreeAndLanguages(); - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }); - }); - } - - //show/hide search results - if (args.key === "showSearchResults") { - $scope.showSearchResults = args.value; - } - - })); - - // Listen for language updates - evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) { - loadLanguages().then(function (languages) { - $scope.languages = languages; - const defaultCulture = $scope.languages[0].culture; - - if (args.language.culture === $scope.selectedLanguage.culture) { - $scope.selectedLanguage = defaultCulture; - - if ($scope.languages.length > 1) { - $location.search("mculture", defaultCulture); - } else { - $location.search("mculture", null); - } - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.path) { - $scope.treeApi.syncTree({ path: currentEditorState.path, activate: true }); - } - } - }); - })); - - //Emitted when a language is created or an existing one saved/edited - evts.push(eventsService.on("editors.languages.languageSaved", function (e, args) { - if(args.isNew){ - //A new language has been created - reload languages for tree - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - else if(args.language.isDefault){ - //A language was saved and was set to be the new default (refresh the tree, so its at the top) - loadLanguages().then(function (languages) { - $scope.languages = languages; - }); - } - })); - - //when a user logs out or timesout - evts.push(eventsService.on("app.notAuthenticated", function () { - $scope.authenticated = false; - })); - - //when the application is ready and the user is authorized, setup the data - //this will occur anytime a new user logs in! - evts.push(eventsService.on("app.ready", function (evt, data) { - $scope.authenticated = true; - ensureInit(); - })); - - // event for infinite editors - evts.push(eventsService.on("appState.editors.open", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("appState.editors.close", function (name, args) { - $scope.infiniteMode = args && args.editors.length > 0 ? true : false; - })); - - evts.push(eventsService.on("treeService.removeNode", function (e, args) { - //check to see if the current page has been removed - - var currentEditorState = editorState.getCurrent(); - if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) { - //current page is loaded, so navigate to root - var section = appState.getSectionState("currentSection"); - $location.path("/" + section); - } - })); - - - - - /** - * Based on the current state of the application, this configures the scope variables that control the main tree and language drop down - */ - function configureTreeAndLanguages() { - - //create the custom query string param for this tree, this is currently only relevant for content - if ($scope.currentSection === "content") { - - //must use $location here because $routeParams isn't available until after the route change - var mainCulture = $location.search().mculture; - //select the current language if set in the query string - if (mainCulture && $scope.languages && $scope.languages.length > 1) { - var found = _.find($scope.languages, function (l) { - if (mainCulture === true) { - return false; - } - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - if (found) { - //set the route param - found.active = true; - $scope.selectedLanguage = found; - } - } - - var queryParams = {}; - if ($scope.selectedLanguage && $scope.selectedLanguage.culture) { - queryParams["culture"] = $scope.selectedLanguage.culture; - } - var queryString = $.param(queryParams); //create the query string from the params object - } - - if (queryString) { - $scope.customTreeParams = queryString; - $scope.treeCacheKey = queryString; // this tree uses caching but we need to change it's cache key per lang - } - else { - $scope.treeCacheKey = "_"; // this tree uses caching, there's no lang selected so use the default - } - - } - - /** - * Called when the app is ready and sets up the navigation (should only be called once) - */ - function ensureInit() { - - //only run once ever! - if (isInit) { - return; - } - - isInit = true; - - var navInit = false; - - //$routeParams will be populated after $routeChangeSuccess since this controller is used outside ng-view, - //* we listen for the first route change with a section to setup the navigation. - //* we listen for all route changes to track the current section. - $rootScope.$on('$routeChangeSuccess', function () { - - //only continue if there's a section available - if ($routeParams.section) { - - if (!navInit) { - navInit = true; - initNav(); - } - - //keep track of the current section when it changes - if ($scope.currentSection != $routeParams.section) { - appState.setSectionState("currentSection", $routeParams.section); - } - - } - }); - } - - /** - * This loads the language data, if the are no variant content types configured this will return no languages - */ - function loadLanguages() { - - return contentResource.allowsCultureVariation().then(function (b) { - if (b === true) { - return languageResource.getAll(); - } else { - return $q.when([]); //resolve an empty collection - } - }); - } - - /** - * Called once during init to initialize the navigation/tree/languages - */ - function initNav() { - // load languages - loadLanguages().then(function (languages) { - - $scope.languages = languages; - - if ($scope.languages.length > 1) { - //if there's already one set, check if it exists - var currCulture = null; - var mainCulture = $location.search().mculture; - if (mainCulture) { - currCulture = _.find($scope.languages, function (l) { - return l.culture.toLowerCase() === mainCulture.toLowerCase(); - }); - } - if (!currCulture) { - // no culture in the request, let's look for one in the cookie that's set when changing language - var defaultCulture = $cookies.get("UMB_MCULTURE"); - if (!defaultCulture || !_.find($scope.languages, function (l) { - return l.culture.toLowerCase() === defaultCulture.toLowerCase(); - })) { - // no luck either, look for the default language - var defaultLang = _.find($scope.languages, function (l) { - return l.isDefault; - }); - if (defaultLang) { - defaultCulture = defaultLang.culture; - } - } - $location.search("mculture", defaultCulture ? defaultCulture : null); - } - } - - $scope.currentSection = $routeParams.section; - - configureTreeAndLanguages(); - - //resolve the tree promise, set it's property values for loading the tree which will make the tree load - treeInitPromise.resolve({ - section: $scope.currentSection, - customTreeParams: $scope.customTreeParams, - cacheKey: $scope.treeCacheKey, - - //because angular doesn't return a promise for the resolve method, we need to resort to some hackery, else - //like normal JS promises we could do resolve(...).then() - onLoaded: function () { - - //the nav is ready, let the app know - eventsService.emit("app.navigationReady", { treeApi: $scope.treeApi }); - - } - }); - }); - } - function nodeExpandedHandler(args) { - //store the reference to the expanded node path - if (args.node) { - treeService._trackExpandedPaths(args.node, expandedPaths); - } - } - - $scope.selectLanguage = function (language) { - - $location.search("mculture", language.culture); - // add the selected culture to a cookie so the user will log back into the same culture later on (cookie lifetime = one year) - var expireDate = new Date(); - expireDate.setDate(expireDate.getDate() + 365); - $cookies.put("UMB_MCULTURE", language.culture, {path: "/", expires: expireDate}); - - // close the language selector - $scope.page.languageSelectorIsOpen = false; - - configureTreeAndLanguages(); //re-bind language to the query string and update the tree params - - //reload the tree with it's updated querystring args - $scope.treeApi.load({ section: $scope.currentSection, customTreeParams: $scope.customTreeParams, cacheKey: $scope.treeCacheKey }).then(function () { - - //re-sync to currently edited node - var currNode = appState.getTreeState("selectedNode"); - //create the list of promises - var promises = []; - //starting with syncing to the currently selected node if there is one - if (currNode) { - var path = treeService.getPath(currNode); - promises.push($scope.treeApi.syncTree({ path: path, activate: true })); - } - // TODO: If we want to keep all paths expanded ... but we need more testing since we need to deal with unexpanding - //for (var i = 0; i < expandedPaths.length; i++) { - // promises.push($scope.treeApi.syncTree({ path: expandedPaths[i], activate: false, forceReload: true })); - //} - //execute them sequentially - - // set selected language to active - angular.forEach($scope.languages, function(language){ - language.active = false; - }); - language.active = true; - - angularHelper.executeSequentialPromises(promises); - }); - - }; - - //this reacts to the options item in the tree - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchShowMenu = function (ev, args) { - //always skip default - args.skipDefault = true; - navigationService.showMenu(args); - }; - - // TODO: migrate to nav service - // TODO: is this used? - $scope.searchHide = function () { - navigationService.hideSearch(); - }; - - //the below assists with hiding/showing the tree - var treeActive = false; - - //Sets a service variable as soon as the user hovers the navigation with the mouse - //used by the leaveTree method to delay hiding - $scope.enterTree = function (event) { - treeActive = true; - }; - - // Hides navigation tree, with a short delay, is cancelled if the user moves the mouse over the tree again - $scope.leaveTree = function (event) { - //this is a hack to handle IE touch events which freaks out due to no mouse events so the tree instantly shuts down - if (!event) { - return; - } - closeTree(); - }; - - $scope.onOutsideClick = function() { - closeTree(); - }; - - function closeTree() { - if (!appState.getGlobalState("touchDevice")) { - treeActive = false; - $timeout(function () { - if (!treeActive) { - navigationService.hideTree(); - } - }, 300); - } - } - - $scope.toggleLanguageSelector = function () { - $scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen; - }; - - //ensure to unregister from all events! - $scope.$on('$destroy', function () { - for (var e in evts) { - eventsService.unsubscribe(evts[e]); - } - }); -} - -//register it -angular.module('umbraco').controller("Umbraco.NavigationController", NavigationController); From 919861bf361115ca2d473fce7ef9787ee6937615 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 31 Mar 2020 17:37:34 +0200 Subject: [PATCH 38/42] Separated member and member group builder and model unit tests, and other minor updates from PR review. --- .../Builders/PropertyGroupBuilder.cs | 2 +- .../Models/DataTypeTests.cs | 19 +-- .../Models/MemberGroupTests.cs | 45 +---- .../Models/MemberTests.cs | 141 +++++----------- .../Builders/DataTypeBuilderTests.cs | 27 +++ .../Builders/MemberBuilderTests.cs | 158 ++++++++++++++++++ .../Builders/MemberGroupBuilderTests.cs | 53 ++++++ 7 files changed, 288 insertions(+), 157 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs diff --git a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs index 4b349a7e8a..5f6fe12dff 100644 --- a/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/PropertyGroupBuilder.cs @@ -7,7 +7,7 @@ using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { public class PropertyGroupBuilder - : ChildBuilderBase, // TODO: likely want to genericise this, so can use for document and media types too. + : ChildBuilderBase, // TODO: likely want to generalise this, so can use for document and media types too. IWithNameBuilder, IWithSortOrderBuilder { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index 53431bfdea..ba1e500d5a 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -11,26 +11,11 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly DataTypeBuilder _builder = new DataTypeBuilder(); - private const int _testId = 3123; - - [Test] - public void Is_Built_Correctly() - { - // Arrange - // Act - var dtd = _builder - .WithId(_testId) - .Build(); - - // Assert - Assert.AreEqual(_testId, dtd.Id); - } - [Test] public void Can_Deep_Clone() { - var dtd = _builder - .WithId(_testId) + var dtd = _builder + .WithId(3123) .Build(); var clone = (DataType) dtd.DeepClone(); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs index a6718660dd..91ccd56b9e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberGroupTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using Newtonsoft.Json; using NUnit.Framework; @@ -14,34 +13,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberGroupBuilder _builder = new MemberGroupBuilder(); - private const int _testId = 6; - private const string _testName = "Test Group"; - private const int _testCreatorId = 4; - private readonly Guid _testKey = Guid.NewGuid(); - private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); - private readonly DateTime _testUpdateDate = DateTime.Now; - private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); - private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); - - [Test] - public void Is_Built_Correctly() - { - // Arrange - // Act - var group = BuildMemberGroup(); - - // Assert - Assert.AreEqual(_testId, group.Id); - Assert.AreEqual(_testKey, group.Key); - Assert.AreEqual(_testName, group.Name); - Assert.AreEqual(_testCreateDate, group.CreateDate); - Assert.AreEqual(_testUpdateDate, group.UpdateDate); - Assert.AreEqual(_testCreatorId, group.CreatorId); - Assert.AreEqual(2, group.AdditionalData.Count); - Assert.AreEqual(_testAdditionalData1.Value, group.AdditionalData[_testAdditionalData1.Key]); - Assert.AreEqual(_testAdditionalData2.Value, group.AdditionalData[_testAdditionalData2.Key]); - } - [Test] public void Can_Deep_Clone() { @@ -81,15 +52,15 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models private MemberGroup BuildMemberGroup() { return _builder - .WithId(_testId) - .WithKey(_testKey) - .WithName(_testName) - .WithCreatorId(_testCreatorId) - .WithCreateDate(_testCreateDate) - .WithUpdateDate(_testUpdateDate) + .WithId(6) + .WithKey(Guid.NewGuid()) + .WithName("Test Group") + .WithCreatorId(4) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) .AddAdditionalData() - .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) - .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs index 9420d03c8f..1ac255db39 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/MemberTests.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Newtonsoft.Json; @@ -16,65 +15,6 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { private readonly MemberBuilder _builder = new MemberBuilder(); - private const int _testMemberTypeId = 99; - private const string _testMemberTypeAlias = "memberType"; - private const string _testMemberTypeName = "Member Type"; - private const string _testMemberTypePropertyGroupName = "Content"; - private const int _testId = 6; - private const string _testName = "Fred"; - private const string _testUsername = "fred"; - private const string _testRawPasswordValue = "raw pass"; - private const string _testEmail = "email@email.com"; - private const int _testCreatorId = 22; - private const int _testLevel = 3; - private const string _testPath = "-1, 4, 10"; - private const bool _testIsApproved = true; - private const bool _testIsLockedOut = true; - private const int _testSortOrder = 5; - private const bool _testTrashed = false; - private readonly Guid _testKey = Guid.NewGuid(); - private readonly DateTime _testCreateDate = DateTime.Now.AddHours(-1); - private readonly DateTime _testUpdateDate = DateTime.Now; - private readonly DateTime _testLastLockoutDate = DateTime.Now.AddHours(-2); - private readonly DateTime _testLastLoginDate = DateTime.Now.AddHours(-3); - private readonly DateTime _testLastPasswordChangeDate = DateTime.Now.AddHours(-4); - private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType1 = ("title", "Title", string.Empty, 1, -88); - private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType2 = ("bodyText", "Body Text", string.Empty, 2, -87); - private readonly (string alias, string name, string description, int sortOrder, int dataTypeId) _testPropertyType3 = ("author", "Author", "Writer of the article", 1, -88); - private readonly string[] _testGroups = new string[] { "group1", "group2" }; - private readonly KeyValuePair _testPropertyData1 = new KeyValuePair("title", "Name member"); - private readonly KeyValuePair _testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); - private readonly KeyValuePair _testPropertyData3 = new KeyValuePair("author", "John Doe"); - private readonly KeyValuePair _testAdditionalData1 = new KeyValuePair("test1", 123); - private readonly KeyValuePair _testAdditionalData2 = new KeyValuePair("test2", "hello"); - - [Test] - public void Is_Built_Correctly() - { - // Arrange - // Act - var member = BuildMember(); - - // Assert - Assert.AreEqual(_testMemberTypeId, member.ContentTypeId); - Assert.AreEqual(_testMemberTypeAlias, member.ContentType.Alias); - Assert.AreEqual(_testMemberTypeName, member.ContentType.Name); - Assert.AreEqual(_testId, member.Id); - Assert.AreEqual(_testKey, member.Key); - Assert.AreEqual(_testName, member.Name); - Assert.AreEqual(_testCreateDate, member.CreateDate); - Assert.AreEqual(_testUpdateDate, member.UpdateDate); - Assert.AreEqual(_testCreatorId, member.CreatorId); - Assert.AreEqual(_testGroups, member.Groups.ToArray()); - Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom - Assert.AreEqual(_testPropertyData1.Value, member.GetValue(_testPropertyData1.Key)); - Assert.AreEqual(_testPropertyData2.Value, member.GetValue(_testPropertyData2.Key)); - Assert.AreEqual(_testPropertyData3.Value, member.GetValue(_testPropertyData3.Key)); - Assert.AreEqual(2, member.AdditionalData.Count); - Assert.AreEqual(_testAdditionalData1.Value, member.AdditionalData[_testAdditionalData1.Key]); - Assert.AreEqual(_testAdditionalData2.Value, member.AdditionalData[_testAdditionalData2.Key]); - } - [Test] public void Can_Deep_Clone() { @@ -141,72 +81,69 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { return _builder .AddMemberType() - .WithId(_testMemberTypeId) - .WithAlias(_testMemberTypeAlias) - .WithName(_testMemberTypeName) + .WithId(99) + .WithAlias("memberType") + .WithName("Member Type") .WithMembershipPropertyGroup() .AddPropertyGroup() - .WithName(_testMemberTypePropertyGroupName) + .WithName("Content") .WithSortOrder(1) .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias(_testPropertyType1.alias) - .WithName(_testPropertyType1.name) - .WithSortOrder(_testPropertyType1.sortOrder) - .WithDataTypeId(_testPropertyType1.dataTypeId) + .WithAlias("title") + .WithName("Title") + .WithSortOrder(1) + .WithDataTypeId(-88) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Ntext) - .WithAlias(_testPropertyType2.alias) - .WithName(_testPropertyType2.name) - .WithSortOrder(_testPropertyType2.sortOrder) - .WithDataTypeId(_testPropertyType2.dataTypeId) + .WithAlias("bodyText") + .WithName("Body text") + .WithSortOrder(2) + .WithDataTypeId(-87) .Done() .AddPropertyType() .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) .WithValueStorageType(ValueStorageType.Nvarchar) - .WithAlias(_testPropertyType3.alias) - .WithName(_testPropertyType3.name) - .WithDescription(_testPropertyType3.description) - .WithSortOrder(_testPropertyType3.sortOrder) - .WithDataTypeId(_testPropertyType3.dataTypeId) + .WithAlias("author") + .WithName("Author") + .WithDescription("Name of the author") + .WithSortOrder(3) + .WithDataTypeId(-88) .Done() .Done() .Done() - .WithId(_testId) - .WithKey(_testKey) - .WithName(_testName) - .WithUserName(_testUsername) - .WithRawPasswordValue(_testRawPasswordValue) - .WithEmail(_testEmail) - .WithCreatorId(_testCreatorId) - .WithCreateDate(_testCreateDate) - .WithUpdateDate(_testUpdateDate) + .WithId(10) + .WithKey(Guid.NewGuid()) + .WithName("Fred") + .WithUserName("fred") + .WithRawPasswordValue("raw pass") + .WithEmail("email@email.com") + .WithCreatorId(22) + .WithCreateDate(DateTime.Now) + .WithUpdateDate(DateTime.Now) .WithFailedPasswordAttempts(22) - .WithLevel(_testLevel) - .WithPath(_testPath) - .WithIsApproved(_testIsApproved) - .WithIsLockedOut(_testIsLockedOut) - .WithLastLockoutDate(_testLastLockoutDate) - .WithLastLoginDate(_testLastLoginDate) - .WithLastPasswordChangeDate(_testLastPasswordChangeDate) - .WithSortOrder(_testSortOrder) - .WithTrashed(_testTrashed) + .WithLevel(3) + .WithPath("-1, 4, 10") + .WithIsApproved(true) + .WithIsLockedOut(true) + .WithSortOrder(5) + .WithTrashed(false) .AddMemberGroups() - .WithValue(_testGroups[0]) - .WithValue(_testGroups[1]) + .WithValue("Group 1") + .WithValue("Group 2") .Done() .AddAdditionalData() - .WithKeyValue(_testAdditionalData1.Key, _testAdditionalData1.Value) - .WithKeyValue(_testAdditionalData2.Key, _testAdditionalData2.Value) + .WithKeyValue("test1", 123) + .WithKeyValue("test2", "hello") .Done() .WithPropertyIdsIncrementingFrom(200) .AddPropertyData() - .WithKeyValue(_testPropertyData1.Key, _testPropertyData1.Value) - .WithKeyValue(_testPropertyData2.Key, _testPropertyData2.Value) - .WithKeyValue(_testPropertyData3.Key, _testPropertyData3.Value) + .WithKeyValue("title", "Name member") + .WithKeyValue("bodyText", "This is a subpage") + .WithKeyValue("author", "John Doe") .Done() .Build(); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs new file mode 100644 index 0000000000..ce47c69763 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/DataTypeBuilderTests.cs @@ -0,0 +1,27 @@ +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class DataTypeBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testId = 3123; + + var builder = new DataTypeBuilder(); + + // Act + var dtd = builder + .WithId(testId) + .Build(); + + // Assert + Assert.AreEqual(testId, dtd.Id); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs new file mode 100644 index 0000000000..e1df867642 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberBuilderTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class MemberBuilderTests + { + private class PropertyTypeDetail + { + public string Alias { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } = string.Empty; + + public int SortOrder { get; set; } + + public int DataTypeId { get; set; } + } + + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testMemberTypeId = 99; + const string testMemberTypeAlias = "memberType"; + const string testMemberTypeName = "Member Type"; + const string testMemberTypePropertyGroupName = "Content"; + const int testId = 10; + const string testName = "Fred"; + const string testUsername = "fred"; + const string testRawPasswordValue = "raw pass"; + const string testEmail = "email@email.com"; + const int testCreatorId = 22; + const int testLevel = 3; + const string testPath = "-1, 4, 10"; + const bool testIsApproved = true; + const bool testIsLockedOut = true; + const int testSortOrder = 5; + const bool testTrashed = false; + var testKey = Guid.NewGuid(); + var testCreateDate = DateTime.Now.AddHours(-1); + var testUpdateDate = DateTime.Now; + var testLastLockoutDate = DateTime.Now.AddHours(-2); + var testLastLoginDate = DateTime.Now.AddHours(-3); + var testLastPasswordChangeDate = DateTime.Now.AddHours(-4); + var testPropertyType1 = new PropertyTypeDetail { Alias = "title", Name = "Title", SortOrder = 1, DataTypeId = -88 }; + var testPropertyType2 = new PropertyTypeDetail { Alias = "bodyText", Name = "Body Text", SortOrder = 2, DataTypeId = -87 }; + var testPropertyType3 = new PropertyTypeDetail { Alias = "author", Name = "Author", Description = "Writer of the article", SortOrder = 1, DataTypeId = -88 }; + var testGroups = new string[] { "group1", "group2" }; + var testPropertyData1 = new KeyValuePair("title", "Name member"); + var testPropertyData2 = new KeyValuePair("bodyText", "This is a subpage"); + var testPropertyData3 = new KeyValuePair("author", "John Doe"); + var testAdditionalData1 = new KeyValuePair("test1", 123); + var testAdditionalData2 = new KeyValuePair("test2", "hello"); + + var builder = new MemberBuilder(); + + // Act + var member = builder + .AddMemberType() + .WithId(testMemberTypeId) + .WithAlias(testMemberTypeAlias) + .WithName(testMemberTypeName) + .WithMembershipPropertyGroup() + .AddPropertyGroup() + .WithName(testMemberTypePropertyGroupName) + .WithSortOrder(1) + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias(testPropertyType1.Alias) + .WithName(testPropertyType1.Name) + .WithSortOrder(testPropertyType1.SortOrder) + .WithDataTypeId(testPropertyType1.DataTypeId) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Ntext) + .WithAlias(testPropertyType2.Alias) + .WithName(testPropertyType2.Name) + .WithSortOrder(testPropertyType2.SortOrder) + .WithDataTypeId(testPropertyType2.DataTypeId) + .Done() + .AddPropertyType() + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithValueStorageType(ValueStorageType.Nvarchar) + .WithAlias(testPropertyType3.Alias) + .WithName(testPropertyType3.Name) + .WithDescription(testPropertyType3.Description) + .WithSortOrder(testPropertyType3.SortOrder) + .WithDataTypeId(testPropertyType3.DataTypeId) + .Done() + .Done() + .Done() + .WithId(testId) + .WithKey(testKey) + .WithName(testName) + .WithUserName(testUsername) + .WithRawPasswordValue(testRawPasswordValue) + .WithEmail(testEmail) + .WithCreatorId(testCreatorId) + .WithCreateDate(testCreateDate) + .WithUpdateDate(testUpdateDate) + .WithFailedPasswordAttempts(22) + .WithLevel(testLevel) + .WithPath(testPath) + .WithIsApproved(testIsApproved) + .WithIsLockedOut(testIsLockedOut) + .WithLastLockoutDate(testLastLockoutDate) + .WithLastLoginDate(testLastLoginDate) + .WithLastPasswordChangeDate(testLastPasswordChangeDate) + .WithSortOrder(testSortOrder) + .WithTrashed(testTrashed) + .AddMemberGroups() + .WithValue(testGroups[0]) + .WithValue(testGroups[1]) + .Done() + .AddAdditionalData() + .WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value) + .WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value) + .Done() + .WithPropertyIdsIncrementingFrom(200) + .AddPropertyData() + .WithKeyValue(testPropertyData1.Key, testPropertyData1.Value) + .WithKeyValue(testPropertyData2.Key, testPropertyData2.Value) + .WithKeyValue(testPropertyData3.Key, testPropertyData3.Value) + .Done() + .Build(); + + // Assert + Assert.AreEqual(testMemberTypeId, member.ContentTypeId); + Assert.AreEqual(testMemberTypeAlias, member.ContentType.Alias); + Assert.AreEqual(testMemberTypeName, member.ContentType.Name); + Assert.AreEqual(testId, member.Id); + Assert.AreEqual(testKey, member.Key); + Assert.AreEqual(testName, member.Name); + Assert.AreEqual(testCreateDate, member.CreateDate); + Assert.AreEqual(testUpdateDate, member.UpdateDate); + Assert.AreEqual(testCreatorId, member.CreatorId); + Assert.AreEqual(testGroups, member.Groups.ToArray()); + Assert.AreEqual(10, member.Properties.Count); // 7 from membership properties group, 3 custom + Assert.AreEqual(testPropertyData1.Value, member.GetValue(testPropertyData1.Key)); + Assert.AreEqual(testPropertyData2.Value, member.GetValue(testPropertyData2.Key)); + Assert.AreEqual(testPropertyData3.Value, member.GetValue(testPropertyData3.Key)); + Assert.AreEqual(2, member.AdditionalData.Count); + Assert.AreEqual(testAdditionalData1.Value, member.AdditionalData[testAdditionalData1.Key]); + Assert.AreEqual(testAdditionalData2.Value, member.AdditionalData[testAdditionalData2.Key]); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs new file mode 100644 index 0000000000..4cb47de052 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.Common/Builders/MemberGroupBuilderTests.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Tests.Common.Builders; +using Umbraco.Tests.Common.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Tests.Common.Builders +{ + [TestFixture] + public class MemberGroupBuilderTests + { + [Test] + public void Is_Built_Correctly() + { + // Arrange + const int testId = 6; + const string testName = "Test Group"; + const int testCreatorId = 4; + var testKey = Guid.NewGuid(); + var testCreateDate = DateTime.Now.AddHours(-1); + var testUpdateDate = DateTime.Now; + var testAdditionalData1 = new KeyValuePair("test1", 123); + var testAdditionalData2 = new KeyValuePair("test2", "hello"); + + var builder = new MemberGroupBuilder(); + + // Act + var group = builder + .WithId(testId) + .WithKey(testKey) + .WithName(testName) + .WithCreatorId(testCreatorId) + .WithCreateDate(testCreateDate) + .WithUpdateDate(testUpdateDate) + .AddAdditionalData() + .WithKeyValue(testAdditionalData1.Key, testAdditionalData1.Value) + .WithKeyValue(testAdditionalData2.Key, testAdditionalData2.Value) + .Done() + .Build(); + + // Assert + Assert.AreEqual(testId, group.Id); + Assert.AreEqual(testKey, group.Key); + Assert.AreEqual(testName, group.Name); + Assert.AreEqual(testCreateDate, group.CreateDate); + Assert.AreEqual(testUpdateDate, group.UpdateDate); + Assert.AreEqual(testCreatorId, group.CreatorId); + Assert.AreEqual(3, group.AdditionalData.Count); // previousName is added as part of the MemberGroup construction, plus the 2 we've added. + Assert.AreEqual(testAdditionalData1.Value, group.AdditionalData[testAdditionalData1.Key]); + Assert.AreEqual(testAdditionalData2.Value, group.AdditionalData[testAdditionalData2.Key]); + } + } +} From 9e38ed045fc708b1cf51ca0875f730111152c9dc Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 08:18:56 +0200 Subject: [PATCH 39/42] Aligned builders --- .../Models/GlobalSettings.cs | 2 +- src/Umbraco.Core/Constants-Configuration.cs | 1 + .../Interfaces/IWithApprovedBuilder.cs | 7 ++ .../Builders/UserBuilder.cs | 94 ++++++++++++------- .../ApplicationBuilderExtensions.cs | 2 +- .../UmbracoCoreServiceCollectionExtensions.cs | 2 - 6 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs diff --git a/src/Umbraco.Configuration/Models/GlobalSettings.cs b/src/Umbraco.Configuration/Models/GlobalSettings.cs index ed1b4e58a2..68d293d104 100644 --- a/src/Umbraco.Configuration/Models/GlobalSettings.cs +++ b/src/Umbraco.Configuration/Models/GlobalSettings.cs @@ -12,7 +12,7 @@ namespace Umbraco.Configuration.Models /// internal class GlobalSettings : IGlobalSettings { - public const string Prefix = Constants.Configuration.ConfigPrefix + "Global:"; + private const string Prefix = Constants.Configuration.ConfigGlobalPrefix; internal const string StaticReservedPaths = "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; //must end with a comma! diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index c45229d918..a34ad8294f 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -12,6 +12,7 @@ /// public const string ConfigPrefix = "Umbraco:CMS:"; public const string ConfigSecurityPrefix = ConfigPrefix+"Security:"; + public const string ConfigGlobalPrefix = ConfigPrefix + "Global:"; public const string ConfigModelsBuilderPrefix = ConfigPrefix+"ModelsBuilder:"; } } diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs new file mode 100644 index 0000000000..48d41e7a84 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithApprovedBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Common.Builders.Interfaces +{ + public interface IWithApprovedBuilder + { + bool? Approved { get; set; } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index 07ab8ef3f7..dd11156bf0 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,16 +1,21 @@ -using System; -using System.Linq; -using Moq; -using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { - public class UserBuilder - : BuilderBase, - IWithIdBuilder + + public class UserBuilder : UserBuilder + { + public UserBuilder() : base(null) + { + } + } + + public class UserBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithNameBuilder, + IWithApprovedBuilder { private int? _id; private string _language; @@ -22,61 +27,57 @@ namespace Umbraco.Tests.Common.Builders private string _username; private string _defaultLang; private string _suffix = string.Empty; + private GlobalSettingsBuilder> _globalSettingsBuilder; - public UserBuilder WithDefaultUILanguage(string defaultLang) + + public UserBuilder(TParent parentBuilder) : base(parentBuilder) + { + _globalSettingsBuilder = new GlobalSettingsBuilder>(this); + } + + public GlobalSettingsBuilder> AddGlobalSettings() => _globalSettingsBuilder; + public UserBuilder WithDefaultUILanguage(string defaultLang) { _defaultLang = defaultLang; return this; } - public UserBuilder WithLanguage(string language) + public UserBuilder WithLanguage(string language) { _language = language; return this; } - public UserBuilder WithApproved(bool approved) - { - _approved = approved; - return this; - } - - public UserBuilder WithRawPassword(string rawPassword) + public UserBuilder WithRawPassword(string rawPassword) { _rawPassword = rawPassword; return this; } - public UserBuilder WithEmail(string email) + public UserBuilder WithEmail(string email) { _email = email; return this; } - public UserBuilder WithUsername(string username) + public UserBuilder WithUsername(string username) { _username = username; return this; } - public UserBuilder WithLockedOut(bool isLockedOut) + public UserBuilder WithLockedOut(bool isLockedOut) { _isLockedOut = isLockedOut; return this; } - public UserBuilder WithName(string name) - { - _name = name; - return this; - } - /// /// Will suffix the name, email and username for testing /// /// /// - public UserBuilder WithSuffix(string suffix) + public UserBuilder WithSuffix(string suffix) { _suffix = suffix; return this; @@ -84,16 +85,25 @@ namespace Umbraco.Tests.Common.Builders public override User Build() { - var globalSettings = Mock.Of(x => x.DefaultUILanguage == (_defaultLang ?? "en-US")); - return new User(globalSettings, - _name ?? "TestUser" + _suffix, - _email ?? "test" + _suffix + "@test.com", - _username ?? "TestUser" + _suffix, - _rawPassword ?? "abcdefghijklmnopqrstuvwxyz") + var globalSettings = _globalSettingsBuilder.Build(); + var name = _name ?? "TestUser" + _suffix; + var email = _email ?? "test" + _suffix + "@test.com"; + var username = _username ?? "TestUser" + _suffix; + var rawPassword = _rawPassword ?? "abcdefghijklmnopqrstuvwxyz"; + var language = _language ?? globalSettings.DefaultUILanguage; + var isLockedOut = _isLockedOut ?? false; + var approved = _approved ?? true; + + return new User( + globalSettings, + name, + email, + username, + rawPassword) { - Language = _language ?? _defaultLang ?? "en-US", - IsLockedOut = _isLockedOut ?? false, - IsApproved = _approved ?? true + Language = language, + IsLockedOut = isLockedOut, + IsApproved = approved }; } @@ -102,5 +112,17 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + + bool? IWithApprovedBuilder.Approved + { + get => _approved; + set => _approved = value; + } } } diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs index 553aa4fe85..a162845200 100644 --- a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs +++ b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Integration.Extensions // dynamically change the config status var umbVersion = app.ApplicationServices.GetRequiredService(); var config = app.ApplicationServices.GetRequiredService(); - config[GlobalSettings.Prefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); + config[Constants.Configuration.ConfigGlobalPrefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString(); // re-run the runtime level check var profilingLogger = app.ApplicationServices.GetRequiredService(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 6f2b31bac1..a0dd1f8e63 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -155,8 +155,6 @@ namespace Umbraco.Web.Common.Extensions backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); profiler = GetWebProfiler(hostingEnvironment, httpContextAccessor); - - Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler); } private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment, IHttpContextAccessor httpContextAccessor) From 6012d6749e00ed75b03b754515557316516d9403 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 11:31:21 +0200 Subject: [PATCH 40/42] Fix views --- .../Umbraco/PartialViewMacros/Templates/Gallery.cshtml | 2 +- src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index 9462b3b638..8388c7a90d 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -1,7 +1,7 @@ @using Umbraco.Core.Models.PublishedContent @using Umbraco.Web @using Umbraco.Core -@using Umbraco.Core.Models +@using Umbraco.Core.Media @using Umbraco.Web.Composing @inherits Umbraco.Web.Macros.PartialViewMacroPage diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml index 0ed54ab958..9d91abc34a 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Editors/Media.cshtml @@ -1,5 +1,5 @@ @model dynamic -@using Umbraco.Web.Templates +@using Umbraco.Core.Media @if (Model.value != null) { @@ -21,7 +21,7 @@ var altText = Model.value.altText ?? Model.value.caption ?? string.Empty; @altText - + if (Model.value.caption != null) {

    @Model.value.caption

    From 944042ad614b7803d47c9e34dfb7ee0b815f22a6 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 12:07:54 +0200 Subject: [PATCH 41/42] Updated builders --- .../Builders/UserBuilder.cs | 11 +++-- .../Builders/UserGroupBuilder.cs | 42 +++++++++++++++++-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs index dd11156bf0..cb24e0e8dc 100644 --- a/src/Umbraco.Tests.Common/Builders/UserBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserBuilder.cs @@ -1,3 +1,4 @@ +using Umbraco.Configuration.Models; using Umbraco.Core.Models.Membership; using Umbraco.Tests.Common.Builders.Interfaces; @@ -25,18 +26,16 @@ namespace Umbraco.Tests.Common.Builders private bool? _isLockedOut; private string _email; private string _username; - private string _defaultLang; private string _suffix = string.Empty; - private GlobalSettingsBuilder> _globalSettingsBuilder; + private string _defaultLang; public UserBuilder(TParent parentBuilder) : base(parentBuilder) { - _globalSettingsBuilder = new GlobalSettingsBuilder>(this); + } - public GlobalSettingsBuilder> AddGlobalSettings() => _globalSettingsBuilder; - public UserBuilder WithDefaultUILanguage(string defaultLang) + public UserBuilder WithDefaultUILanguage(string defaultLang) { _defaultLang = defaultLang; return this; @@ -85,7 +84,7 @@ namespace Umbraco.Tests.Common.Builders public override User Build() { - var globalSettings = _globalSettingsBuilder.Build(); + var globalSettings = new GlobalSettingsBuilder().WithDefaultUiLanguage(_defaultLang).Build(); var name = _name ?? "TestUser" + _suffix; var email = _email ?? "test" + _suffix + "@test.com"; var username = _username ?? "TestUser" + _suffix; diff --git a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs index d3ce5e71a8..4c6bb7a74c 100644 --- a/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/UserGroupBuilder.cs @@ -6,9 +6,20 @@ using Umbraco.Tests.Common.Builders.Interfaces; namespace Umbraco.Tests.Common.Builders { - public class UserGroupBuilder - : BuilderBase, - IWithIdBuilder + + public class UserGroupBuilder : UserGroupBuilder + { + public UserGroupBuilder() : base(null) + { + } + } + + public class UserGroupBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithIconBuilder, + IWithAliasBuilder, + IWithNameBuilder { private int? _startContentId; private int? _startMediaId; @@ -20,12 +31,16 @@ namespace Umbraco.Tests.Common.Builders private string _suffix; private int? _id; + public UserGroupBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + /// /// Will suffix the name and alias for testing /// /// /// - public UserGroupBuilder WithSuffix(string suffix) + public UserGroupBuilder WithSuffix(string suffix) { _suffix = suffix; return this; @@ -61,5 +76,24 @@ namespace Umbraco.Tests.Common.Builders get => _id; set => _id = value; } + + + string IWithIconBuilder.Icon + { + get => _icon; + set => _icon = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } } } From 400fa1ccd40a573ec3f3956d11ae35d8329d6cfe Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 1 Apr 2020 14:19:41 +0200 Subject: [PATCH 42/42] Initialize Current --- src/Umbraco.Tests.Integration/RuntimeTests.cs | 4 ++-- .../Testing/UmbracoIntegrationTest.cs | 2 +- .../UmbracoCoreServiceCollectionExtensions.cs | 19 ++++++++++++++++--- src/Umbraco.Web.UI.NetCore/Startup.cs | 15 ++++++++++++++- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs index 2974a01669..5968ee89fb 100644 --- a/src/Umbraco.Tests.Integration/RuntimeTests.cs +++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs @@ -97,7 +97,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); @@ -136,7 +136,7 @@ namespace Umbraco.Tests.Integration // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs index 43504e908a..d822d687d9 100644 --- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs +++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs @@ -118,7 +118,7 @@ namespace Umbraco.Tests.Integration.Testing // Add it! services.AddUmbracoConfiguration(hostContext.Configuration); - services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly); + services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, out _); }); var host = await hostBuilder.StartAsync(); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index a0dd1f8e63..41581523bb 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -51,13 +51,25 @@ namespace Umbraco.Web.Common.Extensions /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment) + { + return services.AddUmbracoCore(webHostEnvironment,out _); + } + + /// + /// Adds the Umbraco Back Core requirements + /// + /// + /// + /// + /// + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, out IFactory factory) { if (!UmbracoServiceProviderFactory.IsActive) throw new InvalidOperationException("Ensure to add UseUmbraco() in your Program.cs after ConfigureWebHostDefaults to enable Umbraco's service provider factory"); var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer; - return services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly()); + return services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), out factory); } /// @@ -67,8 +79,9 @@ namespace Umbraco.Web.Common.Extensions /// /// /// + /// /// - public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly) + public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); var container = umbContainer; @@ -98,7 +111,7 @@ namespace Umbraco.Web.Common.Extensions backOfficeInfo, typeFinder); - var factory = coreRuntime.Configure(container); + factory = coreRuntime.Configure(container); return services; } diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index 3906f1c8e9..da6e45086f 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -14,11 +14,13 @@ using Umbraco.Composing; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Logging; using Umbraco.Web.BackOffice.AspNetCore; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Extensions; using Umbraco.Web.Common.Runtime.Profiler; using Umbraco.Web.Website.AspNetCore; +using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.UI.BackOffice @@ -47,7 +49,7 @@ namespace Umbraco.Web.UI.BackOffice public void ConfigureServices(IServiceCollection services) { services.AddUmbracoConfiguration(_config); - services.AddUmbracoCore(_webHostEnvironment); + services.AddUmbracoCore(_webHostEnvironment, out var factory); services.AddUmbracoWebsite(); services.AddMvc(); @@ -55,6 +57,17 @@ namespace Umbraco.Web.UI.BackOffice { options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile }); + + //Finally initialize Current + Current.Initialize( + factory.GetInstance (), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance() + ); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.