Merge branch 'main' into v17/dev
# Conflicts: # src/Umbraco.Core/Services/PropertyValidationService.cs # src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs # src/Umbraco.Infrastructure/PublishedContentQuery.cs # src/Umbraco.Web.UI.Client/package-lock.json # src/Umbraco.Web.UI.Client/package.json # src/Umbraco.Web.UI.Client/src/packages/core/tree/default/default-tree.context.ts # src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item/tree-item-base/tree-item-element-base.ts # templates/UmbracoProject/.template.config/template.json # version.json
This commit is contained in:
@@ -26,8 +26,10 @@ public class DecimalPropertyValueEditorTests
|
||||
{ 123, 123m },
|
||||
{ -123, -123m },
|
||||
{ 123.45d, 123.45m },
|
||||
{ 123.45f, 123.45m },
|
||||
{ "123.45", 123.45m },
|
||||
{ "1234.56", 1234.56m },
|
||||
{ "1,234.56", 1234.56m },
|
||||
{ "123,45", 12345m },
|
||||
{ "1.234,56", null },
|
||||
{ "123 45", null },
|
||||
@@ -49,6 +51,18 @@ public class DecimalPropertyValueEditorTests
|
||||
}
|
||||
}
|
||||
|
||||
[SetCulture("it-IT")]
|
||||
[SetUICulture("it-IT")]
|
||||
[TestCase("123,45", 123.45)]
|
||||
[TestCase("1.234,56", 1234.56)]
|
||||
[TestCase("123.45", 12345)]
|
||||
[TestCase("1,234.56", null)]
|
||||
public void Can_Parse_Values_From_Editor_Using_Culture_With_Non_EnUs_Decimal_Separator(object value, decimal? expected)
|
||||
{
|
||||
var fromEditor = FromEditor(value);
|
||||
Assert.AreEqual(expected, fromEditor);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Parse_Values_To_Editor()
|
||||
{
|
||||
|
||||
@@ -128,6 +128,7 @@ public class MultiValuePropertyEditorTests
|
||||
Assert.AreEqual("Item 3", result.Items[2]);
|
||||
}
|
||||
|
||||
[TestCase("", true, "")]
|
||||
[TestCase("Red", true, "")]
|
||||
[TestCase("Yellow", false, "notOneOfOptions")]
|
||||
[TestCase("Red,Green", true, "")]
|
||||
|
||||
@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Routing;
|
||||
using Umbraco.Cms.Core.Services.Navigation;
|
||||
using Umbraco.Cms.Core.Templates;
|
||||
using Umbraco.Cms.Core.Web;
|
||||
using Umbraco.Cms.Tests.Common;
|
||||
using Umbraco.Cms.Tests.UnitTests.TestHelpers.Objects;
|
||||
|
||||
@@ -216,18 +217,204 @@ public class HtmlLocalLinkParserTests
|
||||
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor();
|
||||
|
||||
var umbracoContextFactory = TestUmbracoContextFactory.Create(
|
||||
umbracoContextAccessor: umbracoContextAccessor);
|
||||
|
||||
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
var contentCache = Mock.Get(reference.UmbracoContext.Content);
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(publishedContent.Object);
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent.Object);
|
||||
|
||||
var mediaCache = Mock.Get(reference.UmbracoContext.Media);
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
|
||||
|
||||
var publishedUrlProvider = CreatePublishedUrlProvider(
|
||||
contentUrlProvider,
|
||||
mediaUrlProvider,
|
||||
umbracoContextAccessor);
|
||||
|
||||
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
|
||||
|
||||
var output = linkParser.EnsureInternalLinks(input);
|
||||
|
||||
Assert.AreEqual(result, output);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseLocalLinks_WithUrlMode_RespectsUrlMode()
|
||||
{
|
||||
// Arrange
|
||||
var input = "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world";
|
||||
|
||||
// Setup content URL provider that returns different URLs based on UrlMode
|
||||
var contentUrlProvider = new Mock<IUrlProvider>();
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Relative,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Absolute,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("http://example.com/absolute-url"));
|
||||
|
||||
var contentType = new PublishedContentType(
|
||||
Guid.NewGuid(),
|
||||
666,
|
||||
"alias",
|
||||
PublishedItemType.Content,
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<PublishedPropertyType>(),
|
||||
ContentVariation.Nothing);
|
||||
var publishedContent = new Mock<IPublishedContent>();
|
||||
publishedContent.Setup(x => x.Id).Returns(1234);
|
||||
publishedContent.Setup(x => x.ContentType).Returns(contentType);
|
||||
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor();
|
||||
var umbracoContextFactory = TestUmbracoContextFactory.Create(
|
||||
umbracoContextAccessor: umbracoContextAccessor);
|
||||
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
|
||||
var navigationQueryService = new Mock<IDocumentNavigationQueryService>();
|
||||
// Guid? parentKey = null;
|
||||
// navigationQueryService.Setup(x => x.TryGetParentKey(It.IsAny<Guid>(), out parentKey)).Returns(true);
|
||||
IEnumerable<Guid> ancestorKeys = [];
|
||||
navigationQueryService.Setup(x => x.TryGetAncestorsKeys(It.IsAny<Guid>(), out ancestorKeys)).Returns(true);
|
||||
var publishedUrlProvider = CreatePublishedUrlProvider(
|
||||
contentUrlProvider,
|
||||
new Mock<IMediaUrlProvider>(),
|
||||
umbracoContextAccessor);
|
||||
|
||||
var publishedContentStatusFilteringService = new Mock<IPublishedContentStatusFilteringService>();
|
||||
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
var contentCache = Mock.Get(reference.UmbracoContext.Content);
|
||||
contentCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(publishedContent.Object);
|
||||
|
||||
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
|
||||
|
||||
// Act
|
||||
var relativeOutput = linkParser.EnsureInternalLinks(input, UrlMode.Relative);
|
||||
var absoluteOutput = linkParser.EnsureInternalLinks(input, UrlMode.Absolute);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("hello href=\"/relative-url\" world", relativeOutput);
|
||||
Assert.AreEqual("hello href=\"http://example.com/absolute-url\" world", absoluteOutput);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(UrlMode.Default, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Relative, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Absolute, "hello href=\"{localLink:1234}\" world ", "hello href=\"https://example.com/absolute-url\" world ")]
|
||||
[TestCase(UrlMode.Auto, "hello href=\"{localLink:1234}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Default, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Relative, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Absolute, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"https://example.com/absolute-url\" world ")]
|
||||
[TestCase(UrlMode.Auto, "hello href=\"{localLink:umb://document/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/relative-url\" world ")]
|
||||
[TestCase(UrlMode.Default, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
|
||||
[TestCase(UrlMode.Relative, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
|
||||
[TestCase(UrlMode.Absolute, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"https://example.com/media/absolute/image.jpg\" world ")]
|
||||
[TestCase(UrlMode.Auto, "hello href=\"{localLink:umb://media/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/media/relative/image.jpg\" world ")]
|
||||
public void ParseLocalLinks_WithVariousUrlModes_ReturnsCorrectUrls(UrlMode urlMode, string input, string expectedResult)
|
||||
{
|
||||
// Setup content URL provider that returns different URLs based on UrlMode
|
||||
var contentUrlProvider = new Mock<IUrlProvider>();
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Default,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Relative,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Absolute,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("https://example.com/absolute-url"));
|
||||
contentUrlProvider
|
||||
.Setup(x => x.GetUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
UrlMode.Auto,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/relative-url"));
|
||||
|
||||
var contentType = new PublishedContentType(
|
||||
Guid.NewGuid(),
|
||||
666,
|
||||
"alias",
|
||||
PublishedItemType.Content,
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<PublishedPropertyType>(),
|
||||
ContentVariation.Nothing);
|
||||
var publishedContent = new Mock<IPublishedContent>();
|
||||
publishedContent.Setup(x => x.Id).Returns(1234);
|
||||
publishedContent.Setup(x => x.ContentType).Returns(contentType);
|
||||
|
||||
// Setup media URL provider that returns different URLs based on UrlMode
|
||||
var mediaUrlProvider = new Mock<IMediaUrlProvider>();
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Default,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/media/relative/image.jpg"));
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Relative,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/media/relative/image.jpg"));
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Absolute,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("https://example.com/media/absolute/image.jpg"));
|
||||
mediaUrlProvider.Setup(x => x.GetMediaUrl(
|
||||
It.IsAny<IPublishedContent>(),
|
||||
It.IsAny<string>(),
|
||||
UrlMode.Auto,
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<Uri>()))
|
||||
.Returns(UrlInfo.Url("/media/relative/image.jpg"));
|
||||
|
||||
var mediaType = new PublishedContentType(
|
||||
Guid.NewGuid(),
|
||||
777,
|
||||
"image",
|
||||
PublishedItemType.Media,
|
||||
Enumerable.Empty<string>(),
|
||||
Enumerable.Empty<PublishedPropertyType>(),
|
||||
ContentVariation.Nothing);
|
||||
var media = new Mock<IPublishedContent>();
|
||||
media.Setup(x => x.ContentType).Returns(mediaType);
|
||||
|
||||
var umbracoContextAccessor = new TestUmbracoContextAccessor();
|
||||
var umbracoContextFactory = TestUmbracoContextFactory.Create(
|
||||
umbracoContextAccessor: umbracoContextAccessor);
|
||||
|
||||
var webRoutingSettings = new WebRoutingSettings();
|
||||
|
||||
var publishedUrlProvider = CreatePublishedUrlProvider(
|
||||
contentUrlProvider,
|
||||
mediaUrlProvider,
|
||||
umbracoContextAccessor);
|
||||
|
||||
using (var reference = umbracoContextFactory.EnsureUmbracoContext())
|
||||
{
|
||||
@@ -239,25 +426,35 @@ public class HtmlLocalLinkParserTests
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<int>())).Returns(media.Object);
|
||||
mediaCache.Setup(x => x.GetById(It.IsAny<Guid>())).Returns(media.Object);
|
||||
|
||||
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
|
||||
publishStatusQueryService
|
||||
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
var publishedUrlProvider = new UrlProvider(
|
||||
umbracoContextAccessor,
|
||||
Options.Create(webRoutingSettings),
|
||||
new UrlProviderCollection(() => new[] { contentUrlProvider.Object }),
|
||||
new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
|
||||
Mock.Of<IVariationContextAccessor>(),
|
||||
navigationQueryService.Object,
|
||||
publishedContentStatusFilteringService.Object);
|
||||
|
||||
var linkParser = new HtmlLocalLinkParser(publishedUrlProvider);
|
||||
|
||||
var output = linkParser.EnsureInternalLinks(input);
|
||||
var output = linkParser.EnsureInternalLinks(input, urlMode);
|
||||
|
||||
Assert.AreEqual(result, output);
|
||||
Assert.AreEqual(expectedResult, output);
|
||||
}
|
||||
}
|
||||
|
||||
private static UrlProvider CreatePublishedUrlProvider(
|
||||
Mock<IUrlProvider> contentUrlProvider,
|
||||
Mock<IMediaUrlProvider> mediaUrlProvider,
|
||||
TestUmbracoContextAccessor umbracoContextAccessor)
|
||||
{
|
||||
var navigationQueryService = new Mock<IDocumentNavigationQueryService>();
|
||||
IEnumerable<Guid> ancestorKeys = [];
|
||||
navigationQueryService.Setup(x => x.TryGetAncestorsKeys(It.IsAny<Guid>(), out ancestorKeys)).Returns(true);
|
||||
|
||||
var publishStatusQueryService = new Mock<IPublishStatusQueryService>();
|
||||
publishStatusQueryService
|
||||
.Setup(x => x.IsDocumentPublished(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
return new UrlProvider(
|
||||
umbracoContextAccessor,
|
||||
Options.Create(new WebRoutingSettings()),
|
||||
new UrlProviderCollection(() => new[] { contentUrlProvider.Object }),
|
||||
new MediaUrlProviderCollection(() => new[] { mediaUrlProvider.Object }),
|
||||
Mock.Of<IVariationContextAccessor>(),
|
||||
navigationQueryService.Object,
|
||||
new Mock<IPublishedContentStatusFilteringService>().Object);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Dictionary;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
@@ -27,16 +28,14 @@ public class PropertyValidationServiceTests
|
||||
private void MockObjects(out PropertyValidationService validationService, out IDataType dt)
|
||||
{
|
||||
var dataTypeService = new Mock<IDataTypeService>();
|
||||
var dataType = Mock.Of<IDataType>(
|
||||
x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
|
||||
&& x.DatabaseType == ValueStorageType.Nvarchar
|
||||
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
var dataType = Mock.Of<IDataType>(x => x.ConfigurationObject == string.Empty // irrelevant but needs a value
|
||||
&& x.DatabaseType == ValueStorageType.Nvarchar
|
||||
&& x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
dataTypeService.Setup(x => x.GetDataType(It.IsAny<int>())).Returns(() => dataType);
|
||||
dt = dataType;
|
||||
|
||||
// new data editor that returns a TextOnlyValueEditor which will do the validation for the properties
|
||||
var dataEditor = Mock.Of<IDataEditor>(
|
||||
x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
var dataEditor = Mock.Of<IDataEditor>(x => x.Alias == Constants.PropertyEditors.Aliases.TextBox);
|
||||
Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny<object>()))
|
||||
.Returns(new CustomTextOnlyValueEditor(
|
||||
new DataEditorAttribute(Constants.PropertyEditors.Aliases.TextBox),
|
||||
@@ -44,7 +43,15 @@ public class PropertyValidationServiceTests
|
||||
new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()),
|
||||
Mock.Of<IIOHelper>()));
|
||||
|
||||
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => new[] { dataEditor }));
|
||||
var languageService = new Mock<ILanguageService>();
|
||||
languageService
|
||||
.Setup(s => s.GetDefaultIsoCodeAsync())
|
||||
.ReturnsAsync(() => "en-US");
|
||||
|
||||
var propEditors = new PropertyEditorCollection(new DataEditorCollection(() => [dataEditor]));
|
||||
|
||||
var contentSettings = new Mock<IOptions<ContentSettings>>();
|
||||
contentSettings.Setup(x => x.Value).Returns(new ContentSettings());
|
||||
|
||||
validationService = new PropertyValidationService(
|
||||
propEditors,
|
||||
@@ -52,8 +59,8 @@ public class PropertyValidationServiceTests
|
||||
Mock.Of<ILocalizedTextService>(),
|
||||
new ValueEditorCache(),
|
||||
Mock.Of<ICultureDictionary>(),
|
||||
Mock.Of<ILanguageService>(),
|
||||
Mock.Of<IOptions<ContentSettings>>());
|
||||
languageService.Object,
|
||||
contentSettings.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -279,6 +286,23 @@ public class PropertyValidationServiceTests
|
||||
Assert.AreEqual(4, invalid.Length);
|
||||
}
|
||||
|
||||
[TestCase(null)]
|
||||
[TestCase(24)]
|
||||
[TestCase("test")]
|
||||
[TestCase("{\"test\": true}")]
|
||||
public void ValidatePropertyValue_Always_Returns_No_Validation_Errors_For_Missing_Editor(object? value)
|
||||
{
|
||||
MockObjects(out var validationService, out _);
|
||||
|
||||
var p1 = new PropertyType(ShortStringHelper, "Missing.Alias", ValueStorageType.Ntext)
|
||||
{
|
||||
Variations = ContentVariation.Nothing,
|
||||
};
|
||||
|
||||
var result = validationService.ValidatePropertyValue(p1, value, PropertyValidationContext.Empty());
|
||||
Assert.AreEqual(0, result.Count());
|
||||
}
|
||||
|
||||
// used so we can inject a mock - we should fix the base class DataValueEditor to be able to have the ILocalizedTextField passed
|
||||
// in to create the Requried and Regex validators so we aren't using singletons
|
||||
private class CustomTextOnlyValueEditor : TextOnlyValueEditor
|
||||
|
||||
Reference in New Issue
Block a user