diff --git a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs index 415240e017..e5424fb636 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentErrorPage.cs @@ -24,6 +24,8 @@ public class ContentErrorPage : ValidatableEntryBase /// /// Gets or sets a value for the content XPath. /// + + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public string? ContentXPath { get; set; } /// @@ -39,6 +41,7 @@ public class ContentErrorPage : ValidatableEntryBase /// /// Gets a value indicating whether the field is populated. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public bool HasContentXPath => !string.IsNullOrEmpty(ContentXPath); /// diff --git a/src/Umbraco.Core/Extensions/XmlExtensions.cs b/src/Umbraco.Core/Extensions/XmlExtensions.cs index 34e2b7b2aa..bb9e6c69b5 100644 --- a/src/Umbraco.Core/Extensions/XmlExtensions.cs +++ b/src/Umbraco.Core/Extensions/XmlExtensions.cs @@ -34,6 +34,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList? SelectNodes(this XmlNode source, string expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -56,6 +57,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList? SelectNodes(this XmlNode source, XPathExpression expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -78,6 +80,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList? SelectNodes(this XmlNode source, string? expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -105,6 +108,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList SelectNodes(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -132,6 +136,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, string expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -154,6 +159,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, IEnumerable? variables) { XPathVariable[]? av = variables?.ToArray(); @@ -176,6 +182,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, string expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -202,6 +209,7 @@ public static class XmlExtensions /// /// The XPath expression should reference variables as $var. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNode? SelectSingleNode(this XmlNode source, XPathExpression expression, params XPathVariable[]? variables) { if (variables == null || variables.Length == 0 || variables[0] == null) diff --git a/src/Umbraco.Core/Models/ContentEditing/Language.cs b/src/Umbraco.Core/Models/ContentEditing/Language.cs index 112aeb5aac..99c011d608 100644 --- a/src/Umbraco.Core/Models/ContentEditing/Language.cs +++ b/src/Umbraco.Core/Models/ContentEditing/Language.cs @@ -22,7 +22,7 @@ public class Language [DataMember(Name = "isMandatory")] public bool IsMandatory { get; set; } - [Obsolete("This will be replaced by fallback language ISO code in V13.")] + [Obsolete("This will be replaced by fallback language ISO code in V14.")] [DataMember(Name = "fallbackLanguageId")] public int? FallbackLanguageId { get; set; } } diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 90576a85e3..b0e787de02 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -34,7 +34,7 @@ public class DictionaryItem : EntityBase, IDictionaryItem _translations = new List(); } - [Obsolete("This will be removed in V13.")] + [Obsolete("This will be removed in V14.")] public Func? GetLanguage { get; set; } /// diff --git a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs index 09654d5137..341f185ff9 100644 --- a/src/Umbraco.Core/Models/DictionaryItemExtensions.cs +++ b/src/Umbraco.Core/Models/DictionaryItemExtensions.cs @@ -10,7 +10,7 @@ public static class DictionaryItemExtensions /// /// /// - [Obsolete("This will be replaced in V13 by a corresponding method accepting language ISO code instead of language ID.")] + [Obsolete("This will be replaced in V14 by a corresponding method accepting language ISO code instead of language ID.")] public static string? GetTranslatedValue(this IDictionaryItem d, int languageId) { IDictionaryTranslation? trans = d.Translations.FirstOrDefault(x => x.LanguageId == languageId); @@ -22,7 +22,7 @@ public static class DictionaryItemExtensions /// /// /// - [Obsolete("Warning: This method ONLY works in very specific scenarios. It will be removed in V13.")] + [Obsolete("Warning: This method ONLY works in very specific scenarios. It will be removed in V14.")] public static string? GetDefaultValue(this IDictionaryItem d) { IDictionaryTranslation? defaultTranslation = d.Translations.FirstOrDefault(x => x.Language?.Id == 1); diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index ab79b77e44..7f4471785c 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -1,5 +1,8 @@ using System.Runtime.Serialization; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; +using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Core.Models; @@ -11,6 +14,7 @@ namespace Umbraco.Cms.Core.Models; public class DictionaryTranslation : EntityBase, IDictionaryTranslation { private ILanguage? _language; + private string? _languageIsoCode; // note: this will be memberwise cloned private string _value; @@ -20,6 +24,7 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation _language = language ?? throw new ArgumentNullException("language"); LanguageId = _language.Id; _value = value; + LanguageIsoCode = language.IsoCode; } public DictionaryTranslation(ILanguage language, string value, Guid uniqueId) @@ -27,17 +32,18 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation _language = language ?? throw new ArgumentNullException("language"); LanguageId = _language.Id; _value = value; + LanguageIsoCode = language.IsoCode; Key = uniqueId; } - [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V13.")] + [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V14.")] public DictionaryTranslation(int languageId, string value) { LanguageId = languageId; _value = value; } - [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V13.")] + [Obsolete("Please use constructor that accepts ILanguage. This will be removed in V14.")] public DictionaryTranslation(int languageId, string value, Guid uniqueId) { LanguageId = languageId; @@ -58,7 +64,7 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation /// returned /// on a callback. /// - [Obsolete("This will be removed in V13. From V13 onwards you should get languages by ISO code from ILanguageService.")] + [Obsolete("This will be removed in V14. From V14 onwards you should get languages by ISO code from ILanguageService.")] [DataMember] [DoNotClone] public ILanguage? Language @@ -86,7 +92,7 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation } } - [Obsolete("This will be replaced by language ISO code in V13.")] + [Obsolete("This will be replaced by language ISO code in V14.")] public int LanguageId { get; private set; } /// @@ -99,6 +105,24 @@ public class DictionaryTranslation : EntityBase, IDictionaryTranslation set => SetPropertyValueAndDetectChanges(value, ref _value!, nameof(Value)); } + /// + public string LanguageIsoCode + { + get + { + // TODO: this won't be necessary after obsoleted ctors are removed in v14. + if (_languageIsoCode is null) + { + var _languageService = StaticServiceProvider.Instance.GetRequiredService(); + _languageIsoCode = _languageService.GetLanguageById(LanguageId)?.IsoCode ?? string.Empty; + } + + return _languageIsoCode; + } + + private set => SetPropertyValueAndDetectChanges(value, ref _languageIsoCode!, nameof(LanguageIsoCode)); + } + protected override void PerformDeepClone(object clone) { base.PerformDeepClone(clone); diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs index 45d71e3f9b..8f8d9ffaa4 100644 --- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs @@ -6,18 +6,24 @@ namespace Umbraco.Cms.Core.Models; public interface IDictionaryTranslation : IEntity, IRememberBeingDirty { /// - /// Gets or sets the for the translation + /// Gets or sets the for the translation. /// - [Obsolete("This will be removed in V13. From V13 onwards you should get languages by ISO code from ILanguageService.")] + [Obsolete("This will be removed in V14. From V14 onwards you should get languages by ISO code from ILanguageService.")] [DataMember] ILanguage? Language { get; set; } - [Obsolete("This will be replaced by language ISO code in V13.")] + [Obsolete("This will be replaced by language ISO code in V14.")] int LanguageId { get; } /// - /// Gets or sets the translated text + /// Gets or sets the translated text. /// [DataMember] string Value { get; set; } + + /// + /// Gets the ISO code of the language. + /// + [DataMember] + string LanguageIsoCode => Language?.IsoCode ?? string.Empty; } diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index 88c76ae7b0..885833cd5c 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -55,7 +55,24 @@ public interface ILanguage : IEntity, IRememberBeingDirty /// define fallback strategies when a value does not exist for a requested language. /// /// - [Obsolete("This will be replaced by fallback language ISO code in V13.")] + [Obsolete("This will be replaced by fallback language ISO code in V14.")] [DataMember] int? FallbackLanguageId { get; set; } + + + /// + /// Gets or sets the ISO code of a fallback language. + /// + /// + /// + /// The fallback language can be used in multi-lingual scenarios, to help + /// define fallback strategies when a value does not exist for a requested language. + /// + /// + [DataMember] + string? FallbackIsoCode + { + get => null; + set { } + } } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 9871cf3eed..62a65f086b 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Runtime.Serialization; -using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -14,6 +13,7 @@ public class Language : EntityBase, ILanguage { private string _cultureName; private int? _fallbackLanguageId; + private string? _fallbackLanguageIsoCode; private bool _isDefaultVariantLanguage; private string _isoCode; private bool _mandatory; @@ -74,10 +74,17 @@ public class Language : EntityBase, ILanguage } /// - [Obsolete("This will be replaced by fallback language ISO code in V13.")] + [Obsolete("This will be replaced by fallback language ISO code in V14.")] public int? FallbackLanguageId { get => _fallbackLanguageId; set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, nameof(FallbackLanguageId)); } + + /// + public string? FallbackIsoCode + { + get => _fallbackLanguageIsoCode; + set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageIsoCode, nameof(FallbackIsoCode)); + } } diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs index a7bff33ba4..ed175e418d 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedProperty.cs @@ -69,6 +69,8 @@ public interface IPublishedProperty /// It must be either null, or a string, or an XPathNavigator. /// It has been fully prepared and processed by the appropriate converter. /// + + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] object? GetXPathValue(string? culture = null, string? segment = null); /// diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs index 83ca0c49df..45d36abb6a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedPropertyType.cs @@ -113,6 +113,7 @@ public interface IPublishedPropertyType /// /// The XPath value can be either a string or an XPathNavigator. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs index a06d2006ba..c36809600b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyBase.cs @@ -46,6 +46,7 @@ public abstract class PublishedPropertyBase : IPublishedProperty public abstract object? GetValue(string? culture = null, string? segment = null); /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract object? GetXPathValue(string? culture = null, string? segment = null); /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index 0efa4e7653..848e961d0b 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -273,6 +273,7 @@ namespace Umbraco.Cms.Core.Models.PublishedContent } /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public object? ConvertInterToXPath(IPublishedElement owner, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (!_initialized) diff --git a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs index 3d29744bac..013f969805 100644 --- a/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs +++ b/src/Umbraco.Core/Models/PublishedContent/RawValueProperty.cs @@ -58,6 +58,7 @@ public class RawValueProperty : PublishedPropertyBase public override object? GetValue(string? culture = null, string? segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _objectValue.Value : null; + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) => string.IsNullOrEmpty(culture) & string.IsNullOrEmpty(segment) ? _xpathValue.Value : null; diff --git a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs index 37d6b82475..74dbeaebae 100644 --- a/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/IPropertyValueConverter.cs @@ -128,5 +128,6 @@ public interface IPropertyValueConverter : IDiscoverable /// the cache levels of property values. It is not meant to be used by the converter. /// /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview); } diff --git a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs index 3066086e7b..33c86f3ef4 100644 --- a/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs +++ b/src/Umbraco.Core/PropertyEditors/PropertyValueConverterBase.cs @@ -49,6 +49,7 @@ public abstract class PropertyValueConverterBase : IPropertyValueConverter => inter; /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public virtual object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { var d = new XmlDocument(); diff --git a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs index 6c9c80ce81..fc7651b801 100644 --- a/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/TextStringValueConverter.cs @@ -51,6 +51,7 @@ public class TextStringValueConverter : PropertyValueConverterBase, IDeliveryApi // source should come from ConvertSource and be a string (or null) already inter ?? string.Empty; + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs index 06bfd1b1f2..94f2533548 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ContentPickerValueConverter.cs @@ -86,6 +86,7 @@ public class ContentPickerValueConverter : PropertyValueConverterBase, IDelivery return content ?? inter; } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { if (inter == null) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs index 7941946964..73ef424ba4 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/DatePickerValueConverter.cs @@ -39,6 +39,7 @@ public class DatePickerValueConverter : PropertyValueConverterBase } // default ConvertSourceToObject just returns source ie a DateTime value + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath( IPublishedElement owner, IPublishedPropertyType propertyType, diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs index 3d631afead..e3dcd6ae78 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/MultipleTextStringValueConverter.cs @@ -57,6 +57,7 @@ public class MultipleTextStringValueConverter : PropertyValueConverterBase : values.ToArray(); } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) { var d = new XmlDocument(); diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs index 7503e6711f..8e0aa50acf 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/SimpleTinyMceValueConverter.cs @@ -31,6 +31,7 @@ public class SimpleTinyMceValueConverter : PropertyValueConverterBase // source should come from ConvertSource and be a string (or null) already new HtmlEncodedString(inter == null ? string.Empty : (string)inter); + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs index ab7f99e7f8..7bc940f90f 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/YesNoValueConverter.cs @@ -56,6 +56,7 @@ public class YesNoValueConverter : PropertyValueConverterBase } // default ConvertSourceToObject just returns source ie a boolean value + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a boolean already diff --git a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs index 0ee2ca38ed..78ebd19d6a 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedCache.cs @@ -102,6 +102,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(bool preview, string xpath, params XPathVariable[] vars); /// @@ -111,6 +112,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(string xpath, params XPathVariable[] vars); /// @@ -121,6 +123,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); /// @@ -130,6 +133,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The content, or null. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? GetSingleByXPath(XPathExpression xpath, params XPathVariable[] vars); /// @@ -140,6 +144,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(bool preview, string xpath, params XPathVariable[] vars); /// @@ -149,6 +154,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(string xpath, params XPathVariable[] vars); /// @@ -159,6 +165,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// The value of overrides defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(bool preview, XPathExpression xpath, params XPathVariable[] vars); /// @@ -168,6 +175,7 @@ public interface IPublishedCache : IXPathNavigable /// Optional XPath variables. /// The contents. /// Considers published or unpublished content depending on defaults. + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable GetByXPath(XPathExpression xpath, params XPathVariable[] vars); /// @@ -179,6 +187,7 @@ public interface IPublishedCache : IXPathNavigable /// The value of overrides the context. /// The navigator is already a safe clone (no need to clone it again). /// + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] XPathNavigator CreateNavigator(bool preview); /// @@ -196,6 +205,7 @@ public interface IPublishedCache : IXPathNavigable /// /// If the node does not exist, returns null. /// + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] XPathNavigator? CreateNodeNavigator(int id, bool preview); /// diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs index e4e9010f5b..315136612a 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedContentCache.cs @@ -38,20 +38,26 @@ public sealed class InternalPublishedContentCache : PublishedCacheBase, IPublish public override IEnumerable GetAtRoot(bool preview, string? culture = null) => _content.Values.Where(x => x.Parent == null); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator CreateNavigator(bool preview) => throw new NotImplementedException(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator CreateNodeNavigator(int id, bool preview) => throw new NotImplementedException(); public override bool HasContent(bool preview) => _content.Count > 0; diff --git a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs index a90897e221..c0d76aceb8 100644 --- a/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs +++ b/src/Umbraco.Core/PublishedCache/Internal/InternalPublishedProperty.cs @@ -13,6 +13,7 @@ public class InternalPublishedProperty : IPublishedProperty public bool SolidHasValue { get; set; } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public object? SolidXPathValue { get; set; } public object? SolidDeliveryApiValue { get; set; } @@ -25,6 +26,7 @@ public class InternalPublishedProperty : IPublishedProperty public virtual object? GetValue(string? culture = null, string? segment = null) => SolidValue; + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public virtual object? GetXPathValue(string? culture = null, string? segment = null) => SolidXPathValue; public virtual object? GetDeliveryApiValue(bool expanding, string? culture = null, string? segment = null) => SolidDeliveryApiValue; diff --git a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs index 3e961ce434..c20ebf9284 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedCacheBase.cs @@ -40,31 +40,42 @@ public abstract class PublishedCacheBase : IPublishedCache public IEnumerable GetAtRoot(string? culture = null) => GetAtRoot(PreviewDefault, culture); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? GetSingleByXPath(string xpath, XPathVariable[] vars) => GetSingleByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? GetSingleByXPath(XPathExpression xpath, XPathVariable[] vars) => GetSingleByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable GetByXPath(string xpath, XPathVariable[] vars) => GetByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable GetByXPath(XPathExpression xpath, XPathVariable[] vars) => GetByXPath(PreviewDefault, xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract XPathNavigator CreateNavigator(bool preview); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public XPathNavigator CreateNavigator() => CreateNavigator(PreviewDefault); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public abstract XPathNavigator? CreateNodeNavigator(int id, bool preview); public abstract bool HasContent(bool preview); diff --git a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs index b4e56897a7..05348a138c 100644 --- a/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs +++ b/src/Umbraco.Core/PublishedCache/PublishedElementPropertyBase.cs @@ -201,6 +201,7 @@ internal class PublishedElementPropertyBase : PublishedPropertyBase } } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { GetCacheLevels(out PropertyCacheLevel cacheLevel, out PropertyCacheLevel referenceCacheLevel); diff --git a/src/Umbraco.Core/Xml/DynamicContext.cs b/src/Umbraco.Core/Xml/DynamicContext.cs index fd86866348..321024f486 100644 --- a/src/Umbraco.Core/Xml/DynamicContext.cs +++ b/src/Umbraco.Core/Xml/DynamicContext.cs @@ -118,6 +118,7 @@ namespace Umbraco.Cms.Core.Xml /// /// Same as . /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override bool PreserveWhitespace(XPathNavigator node) { return true; @@ -140,6 +141,7 @@ namespace Umbraco.Cms.Core.Xml /// /// The expression to compile /// A compiled . + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathExpression? Compile(string xpath) { return new XmlDocument().CreateNavigator()?.Compile(xpath); @@ -203,6 +205,7 @@ namespace Umbraco.Cms.Core.Xml /// /// See . Not used in our implementation. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IXsltContextFunction ResolveFunction(string prefix, string name, XPathResultType[] argTypes) => throw new NotImplementedException(); /// diff --git a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs index 2a01d42dc7..87d4d459fa 100644 --- a/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs +++ b/src/Umbraco.Core/Xml/UmbracoXPathPathSyntaxParser.cs @@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.Xml; /// This is used to parse our customize Umbraco XPath expressions (i.e. that include special tokens like $site) into /// a real XPath statement /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class UmbracoXPathPathSyntaxParser { [Obsolete("This will be removed in Umbraco 13. Use ParseXPathQuery which accepts a parentId instead")] diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs index b9359b4fef..8d9d48a0a0 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContent.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContent.cs @@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents a content that can be navigated via XPath. /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableContent { /// diff --git a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs index 08a7c1a0f6..eeb775e07b 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableContentType.cs @@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents the type of a content that can be navigated via XPath. /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableContentType { /// diff --git a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs index 28fa46e84b..faaa612474 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableFieldType.cs @@ -4,6 +4,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// Represents the type of a field of a content that can be navigated via XPath. /// /// A field can be an attribute or a property. +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableFieldType { /// diff --git a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs index 1f8500725b..459f1bf606 100644 --- a/src/Umbraco.Core/Xml/XPath/INavigableSource.cs +++ b/src/Umbraco.Core/Xml/XPath/INavigableSource.cs @@ -3,6 +3,7 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Represents a source of content that can be navigated via XPath. /// +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public interface INavigableSource { /// diff --git a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs index dd27e6124c..3d706eed80 100644 --- a/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/MacroNavigator.cs @@ -7,6 +7,7 @@ namespace Umbraco.Cms.Core.Xml.XPath /// /// Provides a cursor model for navigating {macro /} as if it were XML. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class MacroNavigator : XPathNavigator { private readonly XmlNameTable _nameTable; diff --git a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs index 3529f55922..020f753165 100644 --- a/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/NavigableNavigator.cs @@ -21,6 +21,8 @@ namespace Umbraco.Cms.Core.Xml.XPath; /// /// Provides a cursor model for navigating Umbraco data as if it were XML. /// + +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class NavigableNavigator : XPathNavigator { // "The XmlNameTable stores atomized strings of any local name, namespace URI, diff --git a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs index 1b710c8db5..63fa9ef263 100644 --- a/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs +++ b/src/Umbraco.Core/Xml/XPath/RenamedRootNavigator.cs @@ -3,6 +3,7 @@ using System.Xml.XPath; namespace Umbraco.Cms.Core.Xml.XPath; +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class RenamedRootNavigator : XPathNavigator { private readonly XPathNavigator _navigator; diff --git a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs index 44cda2c691..09bd34f692 100644 --- a/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs +++ b/src/Umbraco.Core/Xml/XPathNavigatorExtensions.cs @@ -18,6 +18,7 @@ public static class XPathNavigatorExtensions /// An XPath expression. /// A set of XPathVariables. /// An iterator over the nodes matching the specified expression. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathNodeIterator Select(this XPathNavigator navigator, string expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) @@ -50,6 +51,7 @@ public static class XPathNavigatorExtensions /// An XPath expression. /// A set of XPathVariables. /// An iterator over the nodes matching the specified expression. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathNodeIterator Select(this XPathNavigator navigator, XPathExpression expression, params XPathVariable[] variables) { if (variables == null || variables.Length == 0 || variables[0] == null) diff --git a/src/Umbraco.Core/Xml/XPathVariable.cs b/src/Umbraco.Core/Xml/XPathVariable.cs index 4c2d2d0f4e..675485e551 100644 --- a/src/Umbraco.Core/Xml/XPathVariable.cs +++ b/src/Umbraco.Core/Xml/XPathVariable.cs @@ -6,6 +6,7 @@ namespace Umbraco.Cms.Core.Xml; /// Represents a variable in an XPath query. /// /// The name must be foo in the constructor and $foo in the XPath query. +[Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public class XPathVariable { /// diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index ad97120c93..a2165fdd39 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -76,6 +76,7 @@ public class XmlHelper /// /// The xml string. /// An XPathDocument created from the xml string. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XPathDocument CreateXPathDocument(string xml) => new XPathDocument(new XmlTextReader(new StringReader(xml))); @@ -85,6 +86,7 @@ public class XmlHelper /// The xml string. /// The XPath document. /// A value indicating whether it has been possible to create the document. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static bool TryCreateXPathDocument(string xml, out XPathDocument? doc) { try @@ -106,6 +108,7 @@ public class XmlHelper /// The XPath document. /// A value indicating whether it has been possible to create the document. /// The value can be anything... Performance-wise, this is bad. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument? doc) { // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling @@ -155,6 +158,7 @@ public class XmlHelper /// The parent node. /// An XPath expression to select children of to sort. /// A function returning the value to order the nodes by. + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static void SortNodes( XmlNode parentNode, string childNodesXPath, @@ -187,6 +191,7 @@ public class XmlHelper /// Assuming all nodes but are sorted, this will move the node to /// the right position without moving all the nodes (as SortNodes would do) - should improve perfs. /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static bool SortNode( XmlNode parentNode, string childNodesXPath, diff --git a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs index 17c2f41843..8031ca0bf0 100644 --- a/src/Umbraco.Core/Xml/XmlNodeListFactory.cs +++ b/src/Umbraco.Core/Xml/XmlNodeListFactory.cs @@ -27,6 +27,7 @@ public class XmlNodeListFactory /// an object inheriting , such as /// . /// + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public static XmlNodeList CreateNodeList(XPathNodeIterator? iterator) => new XmlNodeListIterator(iterator); #endregion Public members diff --git a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs index cc034e5768..6e82239dac 100644 --- a/src/Umbraco.Infrastructure/IPublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/IPublishedContentQuery.cs @@ -18,6 +18,7 @@ public interface IPublishedContentQuery IPublishedContent? Content(object id); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IPublishedContent? ContentSingleAtXPath(string xpath, params XPathVariable[] vars); IEnumerable Content(IEnumerable ids); @@ -26,8 +27,10 @@ public interface IPublishedContentQuery IEnumerable Content(IEnumerable ids); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars); IEnumerable ContentAtRoot(); diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 141200f5f5..2844d53d80 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_7_0; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade; @@ -68,12 +69,21 @@ public class UmbracoPlan : MigrationPlan // To 10.5.0 / 11.2.0 To("{83AF7945-DADE-4A02-9041-F3F6EBFAC319}"); + // to 10.7.0 + To("{EF93F398-1385-4F07-808A-D3C518984442}"); + // To 11.3.0 To("{BB3889ED-E2DE-49F2-8F71-5FD8616A2661}"); // To 11.4.0 To("{FFB6B9B0-F1A8-45E9-9CD7-25700577D1CA}"); + // to 11.4.3 + // This is here twice since it was added in 10, so if you had already upgraded you wouldn't get it + // But we still need the first once, since if you upgraded to 10.7.0 then the "From" state would be unknown otherwise. + // This has it's own key, we essentially consider it a separate migration. + To("{2CA0C5BB-170B-45E5-8179-E73DA4B41A46}"); + // To 12.0.0 To("{888A0D5D-51E4-4C7E-AA0A-01306523C7FB}"); To("{539F2F83-FBA7-4C48-81A3-75081A56BB9D}"); @@ -81,5 +91,8 @@ public class UmbracoPlan : MigrationPlan // To 12.1.0 To("{1187192D-EDB5-4619-955D-91D48D738871}"); To("{47DE85CE-1E16-42A0-8AF6-3EC3BCEF5471}"); + + // And once more for 12 + To("{2D4C9FBD-08B3-472D-A76C-6ED467A0CD20}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs new file mode 100644 index 0000000000..462e3772fa --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_10_7_0/MigrateTagsFromNVarcharToNText.cs @@ -0,0 +1,46 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_10_7_0; + +public class MigrateTagsFromNVarcharToNText : MigrationBase +{ + public MigrateTagsFromNVarcharToNText(IMigrationContext context) + : base(context) + { + } + + protected override void Migrate() + { + // Firstly change the storage type for the Umbraco.Tags property editor + Sql updateDbTypeForTagsQuery = Database.SqlContext.Sql() + .Update(x => x.Set(dt => dt.DbType, ValueStorageType.Ntext.ToString())) + .Where(dt => dt.EditorAlias == Constants.PropertyEditors.Aliases.Tags); + + Database.Execute(updateDbTypeForTagsQuery); + + // Then migrate the data from "varcharValue" column to "textValue" + Sql tagsDataTypeIdQuery = Database.SqlContext.Sql() + .Select(dt => dt.NodeId) + .From() + .Where(dt => dt.EditorAlias == Constants.PropertyEditors.Aliases.Tags); + + Sql tagsPropertyTypeIdQuery = Database.SqlContext.Sql() + .Select(pt => pt.Id) + .From() + .WhereIn(pt => pt.DataTypeId, tagsDataTypeIdQuery); + + Sql updatePropertyDataColumnsQuery = Database.SqlContext.Sql() + .Update() + .Append("SET textValue = varcharValue, varcharValue = null") + .WhereIn(pd => pd.PropertyTypeId, tagsPropertyTypeIdQuery) + .Where(pd => pd.TextValue == null) + .Where(pd => pd.VarcharValue != null); + + Database.Execute(updatePropertyDataColumnsQuery); + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs index 9ab958c306..6ef14238af 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/LanguageFactory.cs @@ -6,7 +6,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Factories; internal static class LanguageFactory { - public static ILanguage BuildEntity(LanguageDto dto) + public static ILanguage BuildEntity(LanguageDto dto, string? fallbackIsoCode) { ArgumentNullException.ThrowIfNull(dto); if (dto.IsoCode is null) @@ -22,6 +22,7 @@ internal static class LanguageFactory IsDefault = dto.IsDefault, IsMandatory = dto.IsMandatory, FallbackLanguageId = dto.FallbackLanguageId, + FallbackIsoCode = fallbackIsoCode }; // Reset dirty initial properties diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index 398a55ebaf..590fae26c0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -120,7 +120,19 @@ internal class LanguageRepository : EntityRepositoryBase, ILangu new FullDataSetRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, GetEntityId, /*expires:*/ false); protected ILanguage ConvertFromDto(LanguageDto dto) - => LanguageFactory.BuildEntity(dto); + { + // yes, we want to lock _codeIdMap + lock (_codeIdMap) + { + string? fallbackIsoCode = null; + if (dto.FallbackLanguageId.HasValue && _idCodeMap.TryGetValue(dto.FallbackLanguageId.Value, out fallbackIsoCode) == false) + { + throw new ArgumentException($"The ISO code map did not contain ISO code for fallback language ID: {dto.FallbackLanguageId}. Please reload the caches."); + } + + return LanguageFactory.BuildEntity(dto, fallbackIsoCode); + } + } // do NOT leak that language, it's not deep-cloned! private ILanguage GetDefault() @@ -172,20 +184,25 @@ internal class LanguageRepository : EntityRepositoryBase, ILangu sql.OrderBy(x => x.Id); // get languages - var languages = Database.Fetch(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); + List? languageDtos = Database.Fetch(sql) ?? new List(); - // initialize the code-id map - lock (_codeIdMap) + // initialize the code-id map if we've reloaded the entire set of languages + if (ids?.Any() == false) { - _codeIdMap.Clear(); - _idCodeMap.Clear(); - foreach (ILanguage language in languages) + lock (_codeIdMap) { - _codeIdMap[language.IsoCode] = language.Id; - _idCodeMap[language.Id] = language.IsoCode.ToLowerInvariant(); + _codeIdMap.Clear(); + _idCodeMap.Clear(); + foreach (LanguageDto languageDto in languageDtos) + { + ArgumentException.ThrowIfNullOrEmpty(languageDto.IsoCode, nameof(LanguageDto.IsoCode)); + _codeIdMap[languageDto.IsoCode] = languageDto.Id; + _idCodeMap[languageDto.Id] = languageDto.IsoCode; + } } } + var languages = languageDtos.Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); return languages; } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs index 1923950fe0..0120d6a2a1 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/TagsPropertyEditor.cs @@ -25,7 +25,8 @@ namespace Umbraco.Cms.Core.PropertyEditors; "Tags", "tags", Icon = "icon-tags", - ValueEditorIsReusable = true)] + ValueEditorIsReusable = true, + ValueType = ValueTypes.Text)] public class TagsPropertyEditor : DataEditor { private readonly IEditorConfigurationParser _editorConfigurationParser; diff --git a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs index 9aeeb02d92..178778483d 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/ValueConverters/MarkdownEditorValueConverter.cs @@ -56,6 +56,7 @@ public class MarkdownEditorValueConverter : PropertyValueConverterBase, IDeliver return new HtmlEncodedString(inter == null ? string.Empty : mark.Transform((string)inter)); } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) => // source should come from ConvertSource and be a string (or null) already diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index ef16aeccc6..d075e8b9d2 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -129,6 +129,7 @@ public class PublishedContentQuery : IPublishedContentQuery return null; } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? ContentSingleAtXPath(string xpath, params XPathVariable[] vars) => ItemByXPath(xpath, vars, _publishedSnapshot.Content); @@ -141,9 +142,11 @@ public class PublishedContentQuery : IPublishedContentQuery public IEnumerable Content(IEnumerable ids) => ids.Select(Content).WhereNotNull(); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) => ItemsByXPath(xpath, vars, _publishedSnapshot.Content); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) => ItemsByXPath(xpath, vars, _publishedSnapshot.Content); diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs index 37c99c64f2..53eecfc8d3 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeState.cs @@ -218,7 +218,7 @@ public class RuntimeState : IRuntimeState if (_globalSettings.Value.InstallMissingDatabase || _databaseProviderMetadata.CanForceCreateDatabase(_databaseFactory)) { // ok to install on a configured but missing database - Level = RuntimeLevel.Install; + Level = RuntimeLevel.BootFailed; Reason = RuntimeLevelReason.InstallMissingDatabase; return; } diff --git a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs index d8a5c0bc04..d854de4b95 100644 --- a/src/Umbraco.PublishedCache.NuCache/ContentCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/ContentCache.cs @@ -360,6 +360,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab ? _snapshot.IsEmpty == false : _snapshot.GetAtRoot().Any(x => x.PublishedModel != null); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -367,6 +368,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return GetSingleByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -386,6 +388,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return xcontent?.InnerContent; } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -393,6 +396,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return GetByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -416,6 +420,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab } } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator CreateNavigator(bool preview) { var source = new Source(this, preview); @@ -423,6 +428,7 @@ public class ContentCache : PublishedCacheBase, IPublishedContentCache, INavigab return navigator; } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override XPathNavigator? CreateNodeNavigator(int id, bool preview) { var source = new Source(this, preview); diff --git a/src/Umbraco.PublishedCache.NuCache/MediaCache.cs b/src/Umbraco.PublishedCache.NuCache/MediaCache.cs index 626e2fe36c..014140e884 100644 --- a/src/Umbraco.PublishedCache.NuCache/MediaCache.cs +++ b/src/Umbraco.PublishedCache.NuCache/MediaCache.cs @@ -95,6 +95,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa #region XPath + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -102,6 +103,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa return GetSingleByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IPublishedContent? GetSingleByXPath(bool preview, XPathExpression xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); @@ -109,6 +111,7 @@ public class MediaCache : PublishedCacheBase, IPublishedMediaCache, INavigableDa return GetSingleByXPath(iterator); } + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override IEnumerable GetByXPath(bool preview, string xpath, XPathVariable[] vars) { XPathNavigator navigator = CreateNavigator(preview); diff --git a/src/Umbraco.PublishedCache.NuCache/Property.cs b/src/Umbraco.PublishedCache.NuCache/Property.cs index d50553d95f..e86f6f4ad9 100644 --- a/src/Umbraco.PublishedCache.NuCache/Property.cs +++ b/src/Umbraco.PublishedCache.NuCache/Property.cs @@ -288,6 +288,7 @@ internal class Property : PublishedPropertyBase return value; } + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public override object? GetXPathValue(string? culture = null, string? segment = null) { _content.VariationContextAccessor.ContextualizeVariation(_variations, _content.Id, ref culture, ref segment); diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index 9325f9e8ae..6dbd5f1e79 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -704,9 +704,6 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers [DataMember(Name = "packageFolder")] public string? PackageFolder { get; set; } - - [DataMember(Name = "sectionAlias")] - public string? SectionAlias { get; set; } } private IEnumerable GetPluginTrees() @@ -738,7 +735,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers continue; } - yield return new PluginTree { Alias = tree.TreeAlias, PackageFolder = pluginController.AreaName, SectionAlias = tree.SectionAlias }; + yield return new PluginTree { Alias = tree.TreeAlias, PackageFolder = pluginController.AreaName }; } } diff --git a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs index 750d60d67e..036397cb4d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/EntityController.cs @@ -533,6 +533,7 @@ public class EntityController : UmbracoAuthorizedJsonController /// /// /// + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public ActionResult? GetByXPath(string query, int nodeContextId, int? parentId, UmbracoEntityTypes type) { if (type != UmbracoEntityTypes.Document) diff --git a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs index a269c3605a..00b924db5d 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/MediaController.cs @@ -583,10 +583,10 @@ public class MediaController : ContentControllerBase Directory.CreateDirectory(root); //must have a file - if (file.Count == 0) + if (file is null || file.Count == 0) { _postAddFileSemaphore.Release(); - return NotFound(); + return NotFound("No file was uploaded"); } //get the string json from the request @@ -769,12 +769,26 @@ public class MediaController : ContentControllerBase break; } - // If media type is still File then let's check if it's an image. + // If media type is still File then let's check if it's an imageor a custom image type. if (mediaTypeAlias == Constants.Conventions.MediaTypes.File && _imageUrlGenerator.IsSupportedImageFormat(ext)) + { + if (allowedContentTypes.Any(mt => mt.Alias == Constants.Conventions.MediaTypes.Image)) { mediaTypeAlias = Constants.Conventions.MediaTypes.Image; } + else + { + IMediaType? customType = allowedContentTypes.FirstOrDefault(mt => + mt.CompositionPropertyTypes.Any(pt => + pt.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)); + + if (customType is not null) + { + mediaTypeAlias = customType.Alias; + } + } + } } else { diff --git a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs index 5708b3c4fd..1125d542da 100644 --- a/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs +++ b/src/Umbraco.Web.BackOffice/Install/InstallApiController.cs @@ -1,5 +1,6 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Umbraco.Cms.Core; @@ -25,8 +26,6 @@ namespace Umbraco.Cms.Web.BackOffice.Install; [Area(Constants.Web.Mvc.InstallArea)] public class InstallApiController : ControllerBase { - private readonly IBackOfficeSignInManager _backOfficeSignInManager; - private readonly IBackOfficeUserManager _backOfficeUserManager; private readonly DatabaseBuilder _databaseBuilder; private readonly InstallStatusTracker _installStatusTracker; private readonly InstallStepCollection _installSteps; @@ -34,6 +33,7 @@ public class InstallApiController : ControllerBase private readonly IProfilingLogger _proflog; private readonly IRuntime _runtime; + [Obsolete("Use the constructor without IBackOfficeUserManager & IBackOfficeSignInManager instead, scheduled for removal in v14")] public InstallApiController( DatabaseBuilder databaseBuilder, IProfilingLogger proflog, @@ -44,14 +44,25 @@ public class InstallApiController : ControllerBase IRuntime runtime, IBackOfficeUserManager backOfficeUserManager, IBackOfficeSignInManager backOfficeSignInManager) + : this(databaseBuilder, proflog, logger, installHelper, installSteps, installStatusTracker, runtime) + { + } + + [ActivatorUtilitiesConstructor] + public InstallApiController( + DatabaseBuilder databaseBuilder, + IProfilingLogger proflog, + ILogger logger, + InstallHelper installHelper, + InstallStepCollection installSteps, + InstallStatusTracker installStatusTracker, + IRuntime runtime) { _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); _installSteps = installSteps; _installStatusTracker = installStatusTracker; _runtime = runtime; - _backOfficeUserManager = backOfficeUserManager; - _backOfficeSignInManager = backOfficeSignInManager; InstallHelper = installHelper; _logger = logger; } @@ -88,20 +99,8 @@ public class InstallApiController : ControllerBase [HttpPost] public async Task CompleteInstall() { - RuntimeLevel levelBeforeRestart = _runtime.State.Level; - await _runtime.RestartAsync(); - if (levelBeforeRestart == RuntimeLevel.Install) - { - BackOfficeIdentityUser? identityUser = - await _backOfficeUserManager.FindByIdAsync(Core.Constants.Security.SuperUserIdAsString); - if (identityUser is not null) - { - _backOfficeSignInManager.SignInAsync(identityUser, false); - } - } - return NoContent(); } diff --git a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs index 28f685bf0d..461d1fc82f 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ApplicationTreeController.cs @@ -360,7 +360,7 @@ public class ApplicationTreeController : UmbracoAuthorizedApiController ControllerActionDescriptor? actionDescriptor = _actionDescriptorCollectionProvider.ActionDescriptors.Items .Cast() .First(x => - (x.ControllerTypeInfo.FullName ?? string.Empty).Equals(controllerType.FullName) && + x.ControllerName.Equals(controllerName) && x.ActionName == action); var actionContext = new ActionContext(HttpContext, routeData, actionDescriptor); diff --git a/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs index 0c46e809d2..630584a839 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ScriptsTreeController.cs @@ -3,12 +3,10 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Web.Common.Attributes; namespace Umbraco.Cms.Web.BackOffice.Trees; [CoreTree] -[PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [Tree(Constants.Applications.Settings, Constants.Trees.Scripts, TreeTitle = "Scripts", SortOrder = 10, TreeGroup = Constants.Trees.Groups.Templating)] public class ScriptsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs index 32f2a3e465..3ff7a7ecfc 100644 --- a/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/StylesheetsTreeController.cs @@ -3,12 +3,10 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Trees; -using Umbraco.Cms.Web.Common.Attributes; namespace Umbraco.Cms.Web.BackOffice.Trees; [CoreTree] -[PluginController(Constants.Web.Mvc.BackOfficeTreeArea)] [Tree(Constants.Applications.Settings, Constants.Trees.Stylesheets, TreeTitle = "Stylesheets", SortOrder = 9, TreeGroup = Constants.Trees.Groups.Templating)] public class StylesheetsTreeController : FileSystemTreeController { diff --git a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs index d8506f5692..1688a99ec2 100644 --- a/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs +++ b/src/Umbraco.Web.BackOffice/Trees/UrlHelperExtensions.cs @@ -47,15 +47,14 @@ public static class UrlHelperExtensions string nodeId, FormCollection? queryStrings) { - var actionName = "GetNodes"; - var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, actionName, treeType); - actionUrl = StartOrContinueQueryString(actionUrl, actionName); - - // Now we need to append the query strings - // Always ignore the custom start node id when generating URLs for tree nodes since this is a custom once-only parameter - // that should only ever be used when requesting a tree to render (root), not a tree node - actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings?.ToQueryString("id", TreeQueryStringParameters.StartNodeId); + var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetNodes", treeType)? + .EnsureEndsWith('?'); + //now we need to append the query strings + actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings?.ToQueryString("id", + //Always ignore the custom start node id when generating URLs for tree nodes since this is a custom once-only parameter + // that should only ever be used when requesting a tree to render (root), not a tree node + TreeQueryStringParameters.StartNodeId); return actionUrl; } @@ -66,29 +65,11 @@ public static class UrlHelperExtensions string nodeId, FormCollection? queryStrings) { - var actionName = "GetMenu"; - var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, actionName, treeType); - actionUrl = StartOrContinueQueryString(actionUrl, actionName); + var actionUrl = urlHelper.GetUmbracoApiService(umbracoApiControllerTypeCollection, "GetMenu", treeType)? + .EnsureEndsWith('?'); - // now we need to append the query strings + //now we need to append the query strings actionUrl += "id=" + nodeId.EnsureEndsWith('&') + queryStrings?.ToQueryString("id"); return actionUrl; } - - /// - /// Check the provided string already includes a querystring fragment - /// If so, result has "&" appended, else has "?" appended. - /// - /// - /// - /// - private static string? StartOrContinueQueryString(string? actionUrl, string? delimiter) - { - if (actionUrl is null) - { - return actionUrl; - } - - return actionUrl.EnsureEndsWith(actionUrl.Contains($"{delimiter}?") ? "&" : "?"); - } } diff --git a/src/Umbraco.Web.Common/UmbracoHelper.cs b/src/Umbraco.Web.Common/UmbracoHelper.cs index 2cb2ea8002..e59e4db588 100644 --- a/src/Umbraco.Web.Common/UmbracoHelper.cs +++ b/src/Umbraco.Web.Common/UmbracoHelper.cs @@ -226,6 +226,7 @@ public class UmbracoHelper private IPublishedContent? ContentForObject(object id) => _publishedContentQuery.Content(id); + [Obsolete("The current implementation of XPath is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IPublishedContent? ContentSingleAtXPath(string xpath, params XPathVariable[] vars) => _publishedContentQuery.ContentSingleAtXPath(xpath, vars); @@ -338,9 +339,11 @@ public class UmbracoHelper /// If an identifier does not match an existing content, it will be missing in the returned value. public IEnumerable Content(IEnumerable ids) => _publishedContentQuery.Content(ids); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(string xpath, params XPathVariable[] vars) => _publishedContentQuery.ContentAtXPath(xpath, vars); + [Obsolete("The current implementation of this method is suboptimal and will be removed entirely in a future version. Scheduled for removal in v14")] public IEnumerable ContentAtXPath(XPathExpression xpath, params XPathVariable[] vars) => _publishedContentQuery.ContentAtXPath(xpath, vars); diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js index 4b8e8fa146..51029234f5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/umbraco.servervariables.js @@ -32,7 +32,7 @@ Umbraco.Sys.ServerVariables = { }, umbracoPlugins: { trees: [ - { alias: "myTree", packageFolder: "MyPackage", sectionAlias: "myPackageSectionAlias" } + { alias: "myTree", packageFolder: "MyPackage" } ] }, isDebuggingEnabled: true, 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 248e78880a..77b97545b6 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 @@ -616,7 +616,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService if (!treeAlias) { throw "Could not get tree alias for node " + args.node.id; } - templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias, args.node.section); + templateUrl = this.getTreeTemplateUrl(treeAlias, args.action.alias); } setMode("dialog"); @@ -633,7 +633,6 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * * @param {string} treeAlias the alias of the tree to look up * @param {string} action the view file name - * @param {string} sectionAlias the alias of the current section * @description * creates the templateUrl based on treeAlias and action * by convention we will look into the /views/{treetype}/{action}.html @@ -641,8 +640,8 @@ function navigationService($routeParams, $location, $q, $injector, eventsService * 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, sectionAlias) { - var packageTreeFolder = treeService.getTreePackageFolder(treeAlias, sectionAlias); + getTreeTemplateUrl: function (treeAlias, action) { + var packageTreeFolder = treeService.getTreePackageFolder(treeAlias); if (packageTreeFolder) { return (Umbraco.Sys.ServerVariables.umbracoSettings.appPluginsPath + "/" + packageTreeFolder + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js index fa5d297a88..ba9ebc1b00 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tree.service.js @@ -166,27 +166,23 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS * * @description * Determines if the current tree is a plugin tree and if so returns the package folder it has declared - * so we know where to find its views, otherwise it will just return undefined. + * so we know where to find it's views, otherwise it will just return undefined. * * @param {String} treeAlias The tree alias to check - * @param {String} sectionAlias The current section */ - getTreePackageFolder: function (treeAlias, sectionAlias) { + getTreePackageFolder: function (treeAlias) { //we determine this based on the server variables - if (!Umbraco.Sys.ServerVariables.umbracoPlugins || !Utilities.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { - return undefined; - } + if (Umbraco.Sys.ServerVariables.umbracoPlugins && + Umbraco.Sys.ServerVariables.umbracoPlugins.trees && + Utilities.isArray(Umbraco.Sys.ServerVariables.umbracoPlugins.trees)) { - let found; - if (sectionAlias !== undefined) { - found = Umbraco.Sys.ServerVariables.umbracoPlugins.trees.find(item => - invariantEquals(item.alias, treeAlias) && invariantEquals(item.sectionAlias, sectionAlias)); - } else { - found = Umbraco.Sys.ServerVariables.umbracoPlugins.trees.find(item => - invariantEquals(item.alias, treeAlias)); - } + var found = _.find(Umbraco.Sys.ServerVariables.umbracoPlugins.trees, function (item) { + return invariantEquals(item.alias, treeAlias); + }); - return found ? found.packageFolder : undefined; + return found ? found.packageFolder : undefined; + } + return undefined; }, /** @@ -872,7 +868,7 @@ function treeService($q, treeResource, iconHelper, notificationsService, eventsS //start var wrappedPromise = doSync(); - //then wrap it + //then wrap it wrappedPromise.then(function (args) { deferred.resolve(args); }, function (args) { diff --git a/src/Umbraco.Web.UI.Client/src/routes.js b/src/Umbraco.Web.UI.Client/src/routes.js index e6d97ea18f..7e65346d1b 100644 --- a/src/Umbraco.Web.UI.Client/src/routes.js +++ b/src/Umbraco.Web.UI.Client/src/routes.js @@ -1,5 +1,5 @@ window.app.config(function ($routeProvider) { - + /** * This determines if the route can continue depending on authentication and initialization requirements * @param {boolean} authRequired If true, it checks if the user is authenticated and will resolve successfully @@ -117,9 +117,9 @@ window.app.config(function ($routeProvider) { template: "
", //This controller will execute for this route, then we can execute some code in order to set the template Url controller: function ($scope, $route, $routeParams, $location, sectionService) { - + //We are going to check the currently loaded sections for the user and if the section we are navigating - //to has a custom route path we'll use that + //to has a custom route path we'll use that sectionService.getSectionsForUser().then(function(sections) { //find the one we're requesting var found = _.find(sections, function(s) { @@ -175,9 +175,8 @@ window.app.config(function ($routeProvider) { if ($routeParams.section.toLowerCase() === "users" && $routeParams.tree.toLowerCase() === "users" && usersPages.indexOf($routeParams.method.toLowerCase()) === -1) { $scope.templateUrl = "views/users/overview.html"; return; - } - - $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method, $routeParams.section); + } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, resolve: canRoute(true) @@ -191,9 +190,8 @@ window.app.config(function ($routeProvider) { if (!$routeParams.tree || !$routeParams.method) { $scope.templateUrl = "views/common/dashboard.html"; return; - } - - $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method, $routeParams.section); + } + $scope.templateUrl = navigationService.getTreeTemplateUrl($routeParams.tree, $routeParams.method); }, reloadOnSearch: false, reloadOnUrl: false, @@ -201,7 +199,7 @@ window.app.config(function ($routeProvider) { }) .otherwise({ redirectTo: '/login' }); }).config(function ($locationProvider) { - + $locationProvider.html5Mode(false); //turn html5 mode off $locationProvider.hashPrefix(''); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html index 56c7a9cf48..be6f21ed96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-sub-views.html @@ -5,7 +5,7 @@ ng-repeat="subView in subViews track by subView.alias" ng-class="'sub-view-' + subView.name" val-sub-view="subView" - ng-if="subView.active" + ng-show="subView.active" >
diff --git a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js index 63800a9e12..4d19cf557a 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/common/services/tree-service.spec.js @@ -298,13 +298,13 @@ describe('tree service tests', function () { it('can find a plugin based tree', function () { //we know this exists in the mock umbraco server vars - var found = treeService.getTreePackageFolder("myTree", "MyPackageSectionAlias"); + var found = treeService.getTreePackageFolder("myTree"); expect(found).toBe("MyPackage"); }); it('returns undefined for a not found tree', function () { //we know this does not exist in the mock umbraco server vars - var found = treeService.getTreePackageFolder("asdfasdf", "fdsafdsa"); + var found = treeService.getTreePackageFolder("asdfasdf"); expect(found).not.toBeDefined(); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index e4feabc9ae..3eca295aac 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -7,8 +7,8 @@ "name": "acceptancetest", "hasInstallScript": true, "dependencies": { - "@umbraco/json-models-builders": "^1.0.5", - "@umbraco/playwright-testhelpers": "^1.0.22", + "@umbraco/json-models-builders": "^1.0.6", + "@umbraco/playwright-testhelpers": "^1.0.24", "camelize": "^1.0.0", "dotenv": "^16.0.2", "faker": "^4.1.0", @@ -132,20 +132,20 @@ "dev": true }, "node_modules/@umbraco/json-models-builders": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.5.tgz", - "integrity": "sha512-14xowT8oiW9+DQVOoundRPvcvnNrU0Ey+06G/q/iZyUnqaNRu/i5nUqcbUZGdv6VBCdxaxq2H3WwtSET3gtneA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-1.0.6.tgz", + "integrity": "sha512-bXwfXcpuqG1Ye714L9KJEGXuSzJfckysE/6CuPjdG8FqHWTE1brv28teR2oMw+ih8ca2u2zUboRgdzLEU/1D3Q==", "dependencies": { "camelize": "^1.0.0", "faker": "^4.1.0" } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.22.tgz", - "integrity": "sha512-hFqqQvEIylagfqFyhQ2rSyYlUP+xpWA5lkhJjkpb2qpxkIISxjwC/FYJTJGvcoBHuUaZrjsSv4lM2aJy2ZWHMA==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-1.0.24.tgz", + "integrity": "sha512-xYOgcnyvcEywgC9DT4Q3OhQDTfdtF1zXLQIXdjNtwr6a4j3SUab1RI/tGxlF01fX+8Ttw3edlV4l+HIrY0hM1Q==", "dependencies": { - "@umbraco/json-models-builders": "^1.0.5", + "@umbraco/json-models-builders": "^1.0.6", "camelize": "^1.0.0", "faker": "^4.1.0", "form-data": "^4.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index f651f799ae..e6e4c264ff 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -19,8 +19,8 @@ "wait-on": "^6.0.1" }, "dependencies": { - "@umbraco/json-models-builders": "^1.0.5", - "@umbraco/playwright-testhelpers": "^1.0.22", + "@umbraco/json-models-builders": "^1.0.6", + "@umbraco/playwright-testhelpers": "^1.0.24", "camelize": "^1.0.0", "faker": "^4.1.0", "form-data": "^4.0.0", diff --git a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts index d084cd69ba..f3d94a05f7 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/playwright.config.ts @@ -10,7 +10,7 @@ dotenv.config(); const config: PlaywrightTestConfig = { testDir: './tests/', /* Maximum time one test can run for. */ - timeout: 30 * 1000, + timeout: 40 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. @@ -27,18 +27,18 @@ const config: PlaywrightTestConfig = { /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: process.env.CI ? 'line' : 'html', outputDir : "./results", - + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, /* Base URL to use in actions like `await page.goto('/')`. */ // baseURL: 'http://localhost:44332', - + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - // When working locally it can be a good idea to use trace: 'on-first-retry' instead of 'retain-on-failure', it can cut the local test times in half. + // When working locally it can be a good idea to use trace: 'on-first-retry' instead of 'retain-on-failure', it can cut the local test times in half. trace: 'retain-on-failure', - ignoreHTTPSErrors: true, + ignoreHTTPSErrors: true, }, /* Configure projects for major browsers */ diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts index 8db631019b..0e18826781 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAdvanced.spec.ts @@ -72,7 +72,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -134,7 +134,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -160,7 +160,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -186,7 +186,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -213,7 +213,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -250,7 +250,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -288,7 +288,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -336,7 +336,7 @@ test.describe('BlockGridEditorAdvancedContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts index a55c12a7d9..7c5b407786 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorAreasContent.spec.ts @@ -6,26 +6,26 @@ import {expect} from "@playwright/test"; test.describe('BlockGridEditorAreasContent', () => { const documentName = 'DocumentTest'; const blockGridName = 'BlockGridTest'; - const elementTitleName = 'ElementTitle'; + const elementTitleName = 'ElementTitle'; const titleText = 'ElementTitle'; const titleArea = 'AreaTitle'; const elementBodyName = 'ElementBody'; const bodyText = 'Lorem ipsum dolor sit amet'; - + const elementBodyAlias = AliasHelper.toAlias(elementBodyName); const documentAlias = AliasHelper.toAlias(documentName); const blockGridAlias = AliasHelper.toAlias(blockGridName); const elementTitleAlias = AliasHelper.toAlias(elementTitleName); - - async function createContentWithABlockInAnotherBlock(umbracoApi,elementParent, elementChild, dataType?, document?) { + + async function createContentWithABlockInAnotherBlock(umbracoApi, elementParent, elementChild, dataType?, document?) { if (dataType == null) { - dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, elementParent, elementChild, blockGridName, titleArea); + dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(elementParent, elementChild, blockGridName, titleArea); } if (document == null) { - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, elementParent, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(elementParent, dataType); } - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -58,7 +58,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); return await umbracoApi.content.save(rootContentNode); } - + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); @@ -80,12 +80,12 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); + + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); - + // Adds a body to the area await page.locator('[data-content-element-type-key="' + element['key'] + '"] >> [data-area-alias="' + titleArea + '"]').click(); await page.locator('[name="infiniteEditorForm"]').locator('[data-element="editor-container"]').getByRole('button', {name: elementBodyName}).click(); @@ -166,9 +166,9 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - + await createContentWithABlockInAnotherBlock(umbracoApi, element, elementBody, dataType); - + await umbracoUi.navigateToContent(blockGridName); // Adds another body to the area @@ -204,7 +204,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -212,12 +212,12 @@ test.describe('BlockGridEditorAreasContent', () => { // Checks if the area in content has the value that is defined in the block grid editor await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + titleArea + '"]')).toHaveAttribute('data-area-col-span', '6'); }); - + test('can add two different blocks in a area with different grid columns', async ({page, umbracoApi, umbracoUi}) => { const secondArea = 'AreaFour'; const columnSpanEight = "8"; const columnSpanFour = "4"; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); @@ -239,8 +239,8 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -275,14 +275,14 @@ test.describe('BlockGridEditorAreasContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Adds a block to the area with a different column span await page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + secondArea + '"]').getByRole('button', {name: 'Add content'}).click(); await page.locator('[name="infiniteEditorForm"]').locator('[data-element="editor-container"]').getByRole('button', {name: elementBodyName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill('BodyTwoText'); await page.locator('[label="Create"]').click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); - + // Assert await umbracoUi.isSuccessNotificationVisible(); // Checks if there are two block in the ElementTitle Area @@ -311,14 +311,14 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); // Checks if the button has the correct row span await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-row-span="3"]').getByRole('button', {name: 'Add content'})).toBeVisible(); - + // Adds a block to the area await page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + titleArea + '"]').getByRole('button', {name: 'Add content'}).click(); await page.locator('[name="infiniteEditorForm"]').locator('[data-element="editor-container"]').getByRole('button', {name: elementBodyName}).click(); @@ -336,7 +336,7 @@ test.describe('BlockGridEditorAreasContent', () => { await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-content-element-type-alias="' + elementBodyAlias + '"]')).toHaveAttribute('data-row-span', '3'); }); }); - + test.describe('Create Button Label', () => { test('can add a create button label for an area in block grid editor', async ({page, umbracoApi, umbracoUi}) => { const newButtonLabel = 'NewAreaBlock'; @@ -358,9 +358,9 @@ test.describe('BlockGridEditorAreasContent', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); - + + await umbracoApi.content.createDefaultContentWithABlockGridEditor( element, dataType, false); + await umbracoUi.navigateToContent(blockGridName); // Assert @@ -371,7 +371,7 @@ test.describe('BlockGridEditorAreasContent', () => { await createButtonLocator.click(); }); }); - + test.describe('Number of blocks', () => { test('can add a minimum number of blocks for a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); @@ -418,7 +418,7 @@ test.describe('BlockGridEditorAreasContent', () => { test('can add a maximum number of blocks for a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const bodyTextTwo = 'AnotherBody'; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); @@ -437,7 +437,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -522,7 +522,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -551,7 +551,7 @@ test.describe('BlockGridEditorAreasContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Adds a ElementBody await page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-area-alias="' + titleArea + '"]').click(); // Since the ElementBody is added as the only Specified Allowance for the area of the Element, then we should be instantly directed to it, instead of having to pick it. @@ -586,7 +586,7 @@ test.describe('BlockGridEditorAreasContent', () => { const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); await createContentWithABlockInAnotherBlock(umbracoApi, element, elementBody, dataType, null); - + await umbracoUi.navigateToContent(blockGridName); // Checks if a validation error is visible @@ -630,7 +630,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -736,7 +736,7 @@ test.describe('BlockGridEditorAreasContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor( element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -778,7 +778,7 @@ test.describe('BlockGridEditorAreasContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Checks if there is validation error for both blocks await expect(page.locator('[key="blockEditor_areaValidationEntriesExceed"]', {hasText: elementBodyName})).toBeVisible(); await expect(page.locator('[key="blockEditor_areaValidationEntriesShort"]', {hasText: elementFooterName})).toBeVisible(); @@ -808,7 +808,7 @@ test.describe('BlockGridEditorAreasContent', () => { await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-content-element-type-alias="' + elementBodyAlias + '"]')).toHaveCount(1); await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"]').locator('[data-content-element-type-alias="' + elementFooterAlias + '"]')).toHaveCount(1); - // Clean + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementFooterName); }); }); @@ -818,9 +818,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -869,9 +869,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -935,9 +935,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -1002,9 +1002,9 @@ test.describe('BlockGridEditorAreasContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementTitleName, elementTitleAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); - const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(umbracoApi, element, elementBody, blockGridName, titleArea); + const dataType = await umbracoApi.dataTypes.createBlockGridDataTypeWithArea(element, elementBody, blockGridName, titleArea); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -1041,14 +1041,14 @@ test.describe('BlockGridEditorAreasContent', () => { const blockParentUdi = rootContentNode.variants[0].properties[0].value.contentData[1].udi; await umbracoUi.navigateToContent(blockGridName); - + // Copies and pastes the block into another block await page.locator('[data-element-udi="' + blockParentUdi + '"]').getByRole('button', {name: 'Copy'}).nth(1).click(); await page.locator('[data-element="property-' + blockGridAlias + '"]').getByRole('button', {name: 'Clipboard'}).click(); await page.locator('[data-element="editor-container"]').locator('umb-block-card', {hasText: elementTitleName}).click(); - + // Checks if the blocks were copied await expect(page.locator('[data-content-element-type-alias="' + elementTitleAlias + '"]')).toHaveCount(2); }); }); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts index 9eb4d75e92..9c610da191 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorContent.spec.ts @@ -27,8 +27,8 @@ test.describe('BlockGridEditorContent', () => { }); test('can create content with a block grid editor', async ({page, umbracoApi, umbracoUi}) => { - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, null, null); - + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(null, null); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -58,11 +58,11 @@ test.describe('BlockGridEditorContent', () => { const newContentValue = 'UpdatedTitle'; const newSettingValue = 'UpdatedSetting'; - const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, null, null, null); + const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(null, null, null); await umbracoUi.navigateToContent(blockGridName); - // Updates the already created content text + // Updates the already created content text await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill(newContentValue); await umbracoUi.clickDataElementByElementName('sub-view-settings'); @@ -71,7 +71,7 @@ test.describe('BlockGridEditorContent', () => { await page.locator('[label="Submit"]').click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); - // Assert + // Assert await umbracoUi.isSuccessNotificationVisible(); // Checks if the Content and Setting were updated after it was saved await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}).click(); @@ -81,13 +81,13 @@ test.describe('BlockGridEditorContent', () => { }); test('can delete a block grid editor in content', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, null, null, null); + const element = await umbracoApi.content.createDefaultContentWithABlockGridEditor(null, null, null); await umbracoUi.navigateToContent(blockGridName); // Deletes the block grid editor inside of the content await page.getByTitle("Delete").click(); - + // Can't use our constant helper because the action for delete does not contain an s. The correct way is 'action-delete' await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey('actions_delete')); @@ -101,7 +101,7 @@ test.describe('BlockGridEditorContent', () => { }); test('can copy block grid content and paste it', async ({page, umbracoApi, umbracoUi}) => { - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, null, null, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(null, null, null); await umbracoUi.navigateToContent(blockGridName); // Checks to make sure that there is only one item @@ -134,7 +134,7 @@ test.describe('BlockGridEditorContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); - const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(umbracoApi, blockGridName, element); + const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(blockGridName, element); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -157,7 +157,7 @@ test.describe('BlockGridEditorContent', () => { .build(); await umbracoApi.documentTypes.save(docType); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, true); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, true); await umbracoUi.navigateToContent(blockGridName); @@ -192,9 +192,9 @@ test.describe('BlockGridEditorContent', () => { const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); // We give the dataType a label so we can differentiate between the top and bottom block in the content editor. - const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(umbracoApi, blockGridName, element, '{{' + element.groups[0].properties[0].alias + '}}'); + const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(blockGridName, element, '{{' + element.groups[0].properties[0].alias + '}}'); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -226,7 +226,7 @@ test.describe('BlockGridEditorContent', () => { await umbracoApi.content.save(rootContentNode); await umbracoUi.navigateToContent(blockGridName); - + // Drag and Drop const dragFromLocator = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: bottomBlock}); const dragToLocator = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: topBlock}); @@ -253,7 +253,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -319,7 +319,7 @@ test.describe('BlockGridEditorContent', () => { await umbracoApi.media.createDefaultImage(imageName); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, null); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, null); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -374,7 +374,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); // Checks if there is validation for needing 2 entries or more @@ -408,7 +408,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -470,7 +470,7 @@ test.describe('BlockGridEditorContent', () => { test.describe('Live editing mode', () => { test('can use live editing mode in content with a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const newText = 'LiveUpdatedContent'; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); const dataTypeBlockGrid = new BlockGridDataTypeBuilder() @@ -485,17 +485,17 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); // Checks if the block contains the correct text before being edited await expect(page.locator('[data-content-element-type-alias="'+elementAlias +'"]')).toContainText('aliasTest'); - + // Updates the text without saving the changes. await page.locator('[data-content-element-type-key="' + element['key'] + '"]').click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill(newText); - + // Checks if the block is being live updated as the content is being updated. await expect(page.locator('[data-content-element-type-alias="'+elementAlias +'"]')).toContainText(newText); }); @@ -515,7 +515,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); @@ -540,7 +540,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); @@ -571,8 +571,8 @@ test.describe('BlockGridEditorContent', () => { .withLayoutStylesheet(stylesheetDataPath) .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); @@ -600,7 +600,7 @@ test.describe('BlockGridEditorContent', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); // Goes to the created Content await umbracoUi.goToSection(ConstantHelper.sections.content); @@ -614,4 +614,4 @@ test.describe('BlockGridEditorContent', () => { await page.locator('[data-element="property-' + blockGridAlias + '"] >> .umb-block-grid__actions', {hasText: newButtonLabel}).click(); }); }); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts index d95d39ccaa..0d8bdd58ac 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorRendering.spec.ts @@ -9,14 +9,14 @@ test.describe('BlockGridEditorRending', () => { const blockGridName = 'BlockGridTest'; const elementTitleName = 'ElementTitle'; const contentText = "ContentTest"; - + const documentAlias = 'documentTest'; const blockGridAlias = 'blockGridTest'; const elementTitleAlias = "elementTitle"; const elementTitleLabel = 'Title'; const elementTitleLabelAlias = AliasHelper.toAlias(elementTitleLabel); - + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); @@ -51,7 +51,7 @@ test.describe('BlockGridEditorRending', () => { .build(); return await umbracoApi.documentTypes.save(element); } - + async function createDocumentWithTemplateAndDataType(umbracoApi, dataType){ const docType = new DocumentTypeBuilder() .withName(documentName) @@ -69,14 +69,14 @@ test.describe('BlockGridEditorRending', () => { .build(); return await umbracoApi.documentTypes.save(docType); } - + async function editDefaultTemplate(umbracoApi){ await umbracoApi.templates.edit(documentName, '@using Umbraco.Cms.Web.Common.PublishedModels;\n' + '@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage\n' + '@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;' + '\n' + '@await Html.GetBlockGridHtmlAsync(Model.' + blockGridName + ')'); } - + async function createPartialViewWithArea(umbracoApi, elementAlias, elementLabelAlias){ const partialViewElementTitle = new PartialViewBuilder() .withName(elementAlias) @@ -91,7 +91,7 @@ test.describe('BlockGridEditorRending', () => { partialViewElementTitle.virtualPath = "/Views/Partials/blockgrid/Components/"; return await umbracoApi.partialViews.save(partialViewElementTitle); } - + async function createPartialViewWithBlock(umbracoApi,elementAlias, elementLabelAlias){ const partialViewElementBody = new PartialViewBuilder() .withName(elementAlias) @@ -105,11 +105,11 @@ test.describe('BlockGridEditorRending', () => { partialViewElementBody.virtualPath = "/Views/Partials/blockgrid/Components/"; await umbracoApi.partialViews.save(partialViewElementBody); } - + test('can render content with a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const element = await createElementWithRTE(umbracoApi, elementTitleName, elementTitleAlias, elementTitleLabel, elementTitleLabelAlias); - const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(umbracoApi, blockGridName, element); + const dataType = await umbracoApi.dataTypes.createDefaultBlockGrid(blockGridName, element); await createDocumentWithTemplateAndDataType(umbracoApi, dataType); @@ -140,7 +140,7 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor.png'); @@ -158,7 +158,7 @@ test.describe('BlockGridEditorRending', () => { const element = await createElementWithRTE(umbracoApi, elementTitleName, elementTitleAlias, elementTitleLabel, elementTitleLabelAlias); const elementBody = await createElementWithRTE(umbracoApi, elementBodyName, elementBodyAlias, elementBodyLabel, elementBodyLabelAlias); - + const dataTypeBlockGrid = new BlockGridDataTypeBuilder() .withName(blockGridName) .addBlock() @@ -179,7 +179,7 @@ test.describe('BlockGridEditorRending', () => { // Creates partial view for the ElementBody await createPartialViewWithBlock(umbracoApi, elementBodyAlias, elementBodyLabelAlias); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -210,12 +210,12 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-two-elements.png'); - // Clean + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.partialViews.ensureNameNotExists('blockgrid/Components', elementBodyAlias + '.cshtml'); }); @@ -257,7 +257,7 @@ test.describe('BlockGridEditorRending', () => { // Creates partial view for the ElementBody await createPartialViewWithBlock(umbracoApi, elementBodyAlias, elementBodyLabelAlias); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -291,12 +291,12 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-area.png'); - - // Clean + + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.partialViews.ensureNameNotExists('blockgrid/Components', elementBodyAlias + '.cshtml'); }) @@ -392,7 +392,7 @@ test.describe('BlockGridEditorRending', () => { .withTemplateAlias(documentName) .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-multiple-areas.png'); @@ -433,7 +433,7 @@ test.describe('BlockGridEditorRending', () => { .done() .done() .addBlock() - .withContentElementTypeKey(elementBody['key']) + .withContentElementTypeKey(elementBody['key']) .addArea() .withAlias('bodyArea') .done() @@ -551,7 +551,7 @@ test.describe('BlockGridEditorRending', () => { .build(); partialViewElement.virtualPath = "/Views/Partials/blockgrid/Components/"; await umbracoApi.partialViews.save(partialViewElement); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -595,7 +595,7 @@ test.describe('BlockGridEditorRending', () => { // ElementBody const elementBodyName = "ElementBody"; const elementBodyAlias = AliasHelper.toAlias(elementBodyName); - + await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.media.ensureNameNotExists(stylesheetTitleName); await umbracoApi.media.ensureNameNotExists(stylesheetBodyName); @@ -695,7 +695,7 @@ test.describe('BlockGridEditorRending', () => { // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-two-custom-stylesheets.png'); - + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); await umbracoApi.media.ensureNameNotExists(stylesheetTitleName); @@ -703,7 +703,7 @@ test.describe('BlockGridEditorRending', () => { await umbracoApi.partialViews.ensureNameNotExists('blockgrid/Components', elementBodyAlias + '.cshtml'); }); }); - + test.describe('Image', () => { test('can render a block grid with an image', async ({page, umbracoApi, umbracoUi}) => { // Image @@ -714,9 +714,9 @@ test.describe('BlockGridEditorRending', () => { const imageMimeType = "image/png"; await umbracoApi.media.ensureNameNotExists(imageName); - + const imageData = await umbracoApi.media.createImageWithFile(imageName, umbracoFileValue, imageFileName, imagePath, imageMimeType); - + const element = new DocumentTypeBuilder() .withName(elementTitleName) .withAlias(elementTitleAlias) @@ -756,7 +756,7 @@ test.describe('BlockGridEditorRending', () => { .build(); partialViewImage.virtualPath = "/Views/Partials/blockgrid/Components/"; await umbracoApi.partialViews.save(partialViewImage); - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.publish) @@ -771,7 +771,7 @@ test.describe('BlockGridEditorRending', () => { .addImage() .withMediaKey(imageData.key) .done() - .done() + .done() .addLayout() .withContentUdi(element['key']) .done() @@ -780,12 +780,12 @@ test.describe('BlockGridEditorRending', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + // Assert await page.goto(umbracoConfig.environment.baseUrl); await expect(page).toHaveScreenshot('Block-grid-editor-with-image.png'); - - // Clean + + // Clean await umbracoApi.media.ensureNameNotExists(imageName); }); }); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts index 358a979a43..0f5773ba04 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockGridEditor/Content/blockGridEditorSettings.spec.ts @@ -29,7 +29,7 @@ test.describe('BlockGridEditorSettings', () => { test.describe('General', () => { test('can see label in content for a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const newLabel = "New Label"; - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); const dataTypeBlockGrid = new BlockGridDataTypeBuilder() @@ -41,14 +41,14 @@ test.describe('BlockGridEditorSettings', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); - + // Assert // Checks if the element contains the correct label await expect(page.locator('[data-content-element-type-alias="' + elementAlias + '"]')).toContainText(newLabel); - // Checks if the element is clickable + // Checks if the element is clickable await page.locator('[data-content-element-type-alias="' + elementAlias + '"]').click(); }); }); @@ -65,9 +65,9 @@ test.describe('BlockGridEditorSettings', () => { .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - - await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(umbracoApi, element, dataType); - + + await umbracoApi.documentTypes.createDefaultDocumentWithBlockGridEditor(element, dataType); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -77,21 +77,21 @@ test.describe('BlockGridEditorSettings', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + await umbracoUi.navigateToContent(blockGridName); - + // Checks if adding a block is disabled await expect(page.locator('[data-element="property-'+blockGridAlias+'"]').locator('umb-block-grid-root').locator('[disabled="disabled"]')).toBeDisabled(); // Checks if the button is not clickable await expect(page.locator('[data-element="property-' + blockGridAlias + '"]').locator('[key="blockEditor_addBlock"]')).not.toBeEnabled(); }); - + test('can set allow in areas to false for an element in a block grid editor', async ({page, umbracoApi, umbracoUi}) => { const elementBodyName = 'BodyElement'; const elementBodyAlias = AliasHelper.toAlias(elementBodyName); - + await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); - + const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); const elementBody = await umbracoApi.documentTypes.createDefaultElementType(elementBodyName, elementBodyAlias); @@ -110,14 +110,14 @@ test.describe('BlockGridEditorSettings', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi,element,dataType,false); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, false); await umbracoUi.navigateToContent(blockGridName); - + // Assert // Checks if the elementTitle is the only element selectable to be in a area await expect(page.locator('[data-area-alias="titleArea"]').locator('[key="blockEditor_addThis"]')).toContainText('Add ' + elementName); - + // Clean await umbracoApi.documentTypes.ensureNameNotExists(elementBodyName); }); @@ -136,20 +136,20 @@ test.describe('BlockGridEditorSettings', () => { .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockGrid); - await umbracoApi.content.createDefaultContentWithABlockGridEditor(umbracoApi, element, dataType, null); + await umbracoApi.content.createDefaultContentWithABlockGridEditor(element, dataType, null); await umbracoUi.navigateToContent(blockGridName); - + // Drags the blocks from a columnSpan of 12 to 6 const dragFrom = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}).locator('[title="Drag to scale"]'); const dragTo = await page.locator('[data-content-element-type-key="' + element['key'] + '"]', {hasText: elementName}); await umbracoUi.dragAndDrop(dragFrom,dragTo, 0, 0 ,10); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); - // Assert + // Assert await umbracoUi.isSuccessNotificationVisible(); // Checks if the block is resized to a column span of 6 await expect(page.locator('[data-content-element-type-key="' + element['key'] + '"], [data-col-span="6"]', {hasText: elementName})).toBeVisible(); }); }); -}); \ No newline at end of file +}); diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts index 8dcadef04d..0c99d46ab5 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/BlockListEditor/blockListEditorContent.spec.ts @@ -6,24 +6,26 @@ import {BlockListDataTypeBuilder} from "@umbraco/json-models-builders/dist/lib/b test.describe('BlockListEditorContent', () => { const documentName = 'DocumentTestName'; const blockListName = 'BlockListTest'; - const elementName = 'TestElement'; + const contentElementName = 'ContentElement'; + const settingElementName = 'SettingsElement'; const documentAlias = AliasHelper.toAlias(documentName); const blockListAlias = AliasHelper.toAlias(blockListName); - // Won't work if I use the to alias for the elementAlias - const elementAlias = 'testElement'; - + // Won't work if we use the to alias for the elementAliases + const contentElementAlias = 'contentElement'; + const settingsElementAlias = 'settingsElement'; + test.beforeEach(async ({page, umbracoApi, umbracoUi}, testInfo) => { await umbracoApi.report.report(testInfo); await umbracoApi.login(); await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); - + test.afterEach(async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.documentTypes.ensureNameNotExists(documentName); - await umbracoApi.documentTypes.ensureNameNotExists(elementName); + await umbracoApi.documentTypes.ensureNameNotExists(contentElementName); await umbracoApi.dataTypes.ensureNameNotExists(blockListName); }); @@ -31,23 +33,22 @@ test.describe('BlockListEditorContent', () => { const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .addBlock() - .withContentElementTypeKey(element['key']) - .withSettingsElementTypeKey(element['key']) + .withContentElementTypeKey(element['key']) + .withSettingsElementTypeKey(element['key']) .done() .build(); return await umbracoApi.dataTypes.save(dataTypeBlockList); } - - async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType){ - + + async function createDocumentWithOneBlockListEditor(umbracoApi, element, dataType, elementName?, elementAlias?){ if(element == null) { element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); } - + if(dataType == null) { dataType = await createDefaultBlockList(umbracoApi, blockListName, element); } - + const docType = new DocumentTypeBuilder() .withName(documentName) .withAlias(documentAlias) @@ -61,16 +62,15 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.documentTypes.save(docType); - + return element; } - - async function createContentWithOneBlockListEditor(umbracoApi, element) { - + + async function createContentWithOneBlockListEditor(umbracoApi, element, datatype?, elementName?, elementAlias?) { if(element == null) { - element = await createDocumentWithOneBlockListEditor(umbracoApi, null, null); + element = await createDocumentWithOneBlockListEditor(umbracoApi, null, datatype, elementName, elementAlias); } - + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -89,13 +89,35 @@ test.describe('BlockListEditorContent', () => { .done() .build(); await umbracoApi.content.save(rootContentNode); - + return element; } + async function createElementWithSlider(umbracoApi, name, alias, sliderId, sliderName?, sliderDefaultValue?) { + if (sliderId == null) { + const sliderData = await umbracoApi.dataTypes.createSliderWithDefaultValue(sliderName, sliderDefaultValue); + sliderId = sliderData.id; + } + + const contentElement = new DocumentTypeBuilder() + .withName(name) + .withAlias(alias) + .AsElementType() + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(sliderId) + .withLabel('Slider') + .withAlias('slider') + .done() + .done() + .build(); + return await umbracoApi.documentTypes.save(contentElement); + } + test('can create content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createDocumentWithOneBlockListEditor(umbracoApi, null, null); - + await createDocumentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); + const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) .withAction(ConstantHelper.actions.save) @@ -113,7 +135,7 @@ test.describe('BlockListEditorContent', () => { await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); // Adds TestElement - await page.locator('[key="blockEditor_addThis"]', {hasText: elementName}).click(); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); await page.locator('[id="sub-view-0"]').locator('[id="title"]').fill('Testing...'); await page.locator('[label="Create"]').click(); @@ -121,17 +143,17 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content was created await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); - await expect(page.locator('.umb-block-list__block--view').nth(0)).toHaveText(elementName); + await expect(page.locator('.umb-block-list__block--view')).toHaveText(contentElementName); // Checks if the content contains the correct value - await page.locator('.umb-block-list__block--view').nth(0).click(); + await page.locator('.umb-block-list__block--view', {hasText: contentElementName}).click(); await expect(page.locator('[id="sub-view-0"]').locator('[id="title"]')).toHaveValue('Testing...'); }); test('can update content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -154,7 +176,7 @@ test.describe('BlockListEditorContent', () => { }); test('can delete a block list editor in content', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -172,13 +194,13 @@ test.describe('BlockListEditorContent', () => { // Assert await umbracoUi.isSuccessNotificationVisible(); - + // Checks if the content is actually deleted await expect(page.locator('[ui-sortable="vm.sortableOptions"]').nth(0)).not.toBeVisible(); }); test('can copy block list content and paste it', async ({page, umbracoApi, umbracoUi}) => { - await createContentWithOneBlockListEditor(umbracoApi, null); + await createContentWithOneBlockListEditor(umbracoApi, null, null, contentElementName, contentElementAlias); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -194,7 +216,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes block list content await page.locator('[title="Clipboard"]').click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); // Assert @@ -204,9 +226,9 @@ test.describe('BlockListEditorContent', () => { }); test('can copy block list content and paste it into another group with the same block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -227,7 +249,7 @@ test.describe('BlockListEditorContent', () => { .build(); await umbracoApi.documentTypes.save(docType); - await createContentWithOneBlockListEditor(umbracoApi, element); + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -245,7 +267,7 @@ test.describe('BlockListEditorContent', () => { await expect(page.locator('.alert-success', {hasText: 'Copied to clipboard'})).toBeVisible(); // Pastes into the second group await page.locator('[title="Clipboard"]').nth(1).click(); - await page.locator('umb-block-card', {hasText: elementName}).click(); + await page.locator('umb-block-card', {hasText: contentElementName}).click(); await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); await expect(page.locator('.alert-success', {hasText: 'Content Published'})).toBeVisible(); @@ -257,20 +279,20 @@ test.describe('BlockListEditorContent', () => { }); test('can set a minimum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMin(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -294,18 +316,18 @@ test.describe('BlockListEditorContent', () => { }); test('can set a maximum of required blocks in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withMax(2) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); const rootContentNode = new ContentBuilder() .withContentTypeAlias(documentAlias) @@ -317,16 +339,16 @@ test.describe('BlockListEditorContent', () => { .withAlias(blockListAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTest") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTest") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTests") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTests") .done() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "aliasTester") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "aliasTester") .done() .done() .done() @@ -357,20 +379,20 @@ test.describe('BlockListEditorContent', () => { }); test('can use inline editing mode in content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { - const element = await umbracoApi.documentTypes.createDefaultElementType(elementName, elementAlias); + const contentElement = await umbracoApi.documentTypes.createDefaultElementType(contentElementName, contentElementAlias); const dataTypeBlockList = new BlockListDataTypeBuilder() .withName(blockListName) .withUseInlineEditingAsDefault(true) .addBlock() - .withContentElementTypeKey(element['key']) + .withContentElementTypeKey(contentElement['key']) .done() .build(); const dataType = await umbracoApi.dataTypes.save(dataTypeBlockList); - await createDocumentWithOneBlockListEditor(umbracoApi, element, dataType); - - await createContentWithOneBlockListEditor(umbracoApi, element); + await createDocumentWithOneBlockListEditor(umbracoApi, contentElement, dataType); + + await createContentWithOneBlockListEditor(umbracoApi, contentElement); await umbracoUi.goToSection(ConstantHelper.sections.content); await umbracoUi.refreshContentTree(); @@ -387,11 +409,11 @@ test.describe('BlockListEditorContent', () => { test('can see rendered content with a block list editor', async ({page, umbracoApi, umbracoUi}) => { await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); - const element = new DocumentTypeBuilder() - .withName(elementName) - .withAlias(elementAlias) + const contentElement = new DocumentTypeBuilder() + .withName(contentElementName) + .withAlias(contentElementAlias) .AsElementType() .addGroup() .withName("TestString") @@ -406,9 +428,9 @@ test.describe('BlockListEditorContent', () => { .done() .done() .build(); - await umbracoApi.documentTypes.save(element); + await umbracoApi.documentTypes.save(contentElement); - const dataType = await createDefaultBlockList(umbracoApi, blockListName, element); + const dataType = await createDefaultBlockList(umbracoApi, blockListName, contentElement); const docType = new DocumentTypeBuilder() .withName(documentName) @@ -418,7 +440,7 @@ test.describe('BlockListEditorContent', () => { .addGroup() .withName('BlockListGroup') .addCustomProperty(dataType['id']) - .withAlias(elementAlias) + .withAlias(contentElementAlias) .done() .done() .build(); @@ -431,15 +453,15 @@ test.describe('BlockListEditorContent', () => { '\n Layout = null;' + '\n}' + '\n' + - '@Html.GetBlockListHtml(Model.' + elementName + ')'); + '@Html.GetBlockListHtml(Model.' + contentElementName + ')'); const partialView = new PartialViewBuilder() - .withName(elementAlias) + .withName(contentElementAlias) .withContent("@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage;\n" + "@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;\n" + "@{\n" + - "var content = (ContentModels." + elementName + ")Model.Content;\n" + - "var settings = (ContentModels." + elementName + ")Model.Settings;\n" + + "var content = (ContentModels." + contentElementName + ")Model.Content;\n" + + "var settings = (ContentModels." + contentElementName + ")Model.Settings;\n" + "}\n" + "\n" + "

@content.Title

" + @@ -459,15 +481,15 @@ test.describe('BlockListEditorContent', () => { .withSave(true) .withPublish(true) .addProperty() - .withAlias(elementAlias) + .withAlias(contentElementAlias) .addBlockListValue() .addBlockListEntry() - .withContentTypeKey(element['key']) - .appendContentProperties(element.groups[0].properties[0].alias, "ContentTest") - .appendContentProperties(element.groups[0].properties[1].alias, "RTEContent") - .withSettingsTypeKey(element['key']) - .appendSettingsProperties(element.groups[0].properties[0].alias, "SettingTest") - .appendSettingsProperties(element.groups[0].properties[1].alias, "RTESetting") + .withContentTypeKey(contentElement['key']) + .appendContentProperties(contentElement.groups[0].properties[0].alias, "ContentTest") + .appendContentProperties(contentElement.groups[0].properties[1].alias, "RTEContent") + .withSettingsTypeKey(contentElement['key']) + .appendSettingsProperties(contentElement.groups[0].properties[0].alias, "SettingTest") + .appendSettingsProperties(contentElement.groups[0].properties[1].alias, "RTESetting") .done() .done() .done() @@ -479,9 +501,83 @@ test.describe('BlockListEditorContent', () => { // Ensure that the view gets rendered correctly const expected = `

ContentTest

RTEContent

SettingTest

RTESetting

`; await expect(await umbracoApi.content.verifyRenderedContent('/', expected, true)).toBeTruthy(); - + // Clean await umbracoApi.templates.ensureNameNotExists(documentName); - await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',elementAlias + '.cshtml'); + await umbracoApi.partialViews.ensureNameNotExists('blocklist/Components',contentElementAlias + '.cshtml'); }); -}); \ No newline at end of file + + test('Checks if the settings content model contains a default value when created', async ({page, umbracoApi, umbracoUi}) => { + const contentSliderName = 'ContentSlider'; + const contentSliderDefaultValue = 5; + const settingsSliderName = 'SettingsSlider'; + const settingsSliderDefaultValue = 9; + + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + + const contentElement = await createElementWithSlider(umbracoApi, contentElementName, contentElementAlias, null, contentSliderName, contentSliderDefaultValue); + + const settingsElement = await createElementWithSlider(umbracoApi, settingElementName, settingsElementAlias, null, settingsSliderName, settingsSliderDefaultValue); + + const blockList = new BlockListDataTypeBuilder() + .withName(blockListName) + .addBlock() + .withContentElementTypeKey(contentElement['key']) + .withSettingsElementTypeKey(settingsElement['key']) + .done() + .build(); + const blockListDataType = await umbracoApi.dataTypes.save(blockList); + + const document = new DocumentTypeBuilder() + .withName(documentName) + .withAlias(documentAlias) + .withAllowAsRoot(true) + .addGroup() + .withName('Content') + .withAlias('content') + .addCustomProperty(blockListDataType['id']) + .withLabel('BlockListTest') + .withAlias('blockListTest') + .done() + .done() + .build(); + await umbracoApi.documentTypes.save(document); + + const content = new ContentBuilder() + .withContentTypeAlias(documentAlias) + .withAction(ConstantHelper.actions.publish) + .addVariant() + .withName(blockListName) + .withSave(true) + .withPublish(true) + .done() + .build(); + const contentNode = await umbracoApi.content.save(content); + + await umbracoUi.goToSection(ConstantHelper.sections.content); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await page.locator('[key="blockEditor_addThis"]', {hasText: contentElementName}).click(); + await page.locator('[label="Create"]').click(); + await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish)); + + // Assert + // Checks if the content contains the BlockListEditor block + await umbracoUi.clickDataElementByElementName("tree-root"); + await umbracoUi.refreshContentTree(); + await umbracoUi.clickDataElementByElementName('tree-item-' + blockListName); + await expect(page.locator('.umb-block-list__block--view')).toHaveCount(1); + + // Checks if both the content and settings have been assigned their DefaultValues + const contentData = await umbracoApi.content.getContent(contentNode.id); + await expect(contentData.variants[0].tabs[0].properties[0].value.contentData[0].slider).toEqual(contentSliderDefaultValue.toString()); + await expect(contentData.variants[0].tabs[0].properties[0].value.settingsData[0].slider).toEqual(settingsSliderDefaultValue.toString()); + + // Clean + await umbracoApi.documentTypes.ensureNameNotExists(settingElementName); + await umbracoApi.dataTypes.ensureNameNotExists(contentSliderName); + await umbracoApi.dataTypes.ensureNameNotExists(settingsSliderName); + }); +}); diff --git a/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs b/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs index 16283e0adf..0aa81a7381 100644 --- a/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs +++ b/tests/Umbraco.Tests.Common/Builders/LanguageBuilder.cs @@ -95,7 +95,7 @@ public class LanguageBuilder return this; } - [Obsolete("This will be replaced in V13 by a corresponding method accepting language ISO code instead of language ID.")] + [Obsolete("This will be replaced in V14 by a corresponding method accepting language ISO code instead of language ID.")] public LanguageBuilder WithFallbackLanguageId(int fallbackLanguageId) { _fallbackLanguageId = fallbackLanguageId;