From f64f6349795279f0e778139ce7e81dcc9827bdc2 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 22 Mar 2023 01:38:10 +0200 Subject: [PATCH 01/35] Fix equality check on property group (#13133) * Fix equality check on property group Also fix circular reference on PropertyTypeCollection --- src/Umbraco.Core/Models/PropertyGroup.cs | 3 ++- src/Umbraco.Core/Models/PropertyTypeCollection.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 034770cdfc..9d23c85a9b 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -2,6 +2,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; + using Umbraco.Cms.Core.Models.Entities; namespace Umbraco.Cms.Core.Models; @@ -132,7 +133,7 @@ public class PropertyGroup : EntityBase, IEquatable } public bool Equals(PropertyGroup? other) => - base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias); + base.Equals(other) || (other != null && Type == other.Type && Alias == other.Alias && Id == other.Id); public override int GetHashCode() => (base.GetHashCode(), Type, Alias).GetHashCode(); diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 49c83b4c9d..77a5d84421 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -30,7 +30,7 @@ public class PropertyTypeCollection : KeyedCollection, IN // This baseclass calling is needed, else compiler will complain about nullability /// - public bool IsReadOnly => ((ICollection)this).IsReadOnly; + public bool IsReadOnly => false; // 'new' keyword is required! we can explicitly implement ICollection.Add BUT since normally a concrete PropertyType type // is passed in, the explicit implementation doesn't get called, this ensures it does get called. From 031beee989eb8cfcff6aafa6cb25f949459c2c1c Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Thu, 23 Mar 2023 10:38:18 +0100 Subject: [PATCH 02/35] Improve ImageSharpImageUrlGenerationTests (#13332) * Improve ImageSharpImageUrlGenerationTests Removed a few "stale" tests that look to have been from code of old. Add tests for all ImageUrlGenerationOptions. Add some edge case tests to make the tests more sensitive. Reduce scope for certain tests so the test is more focused on one thing rather than multiple * Amending a mis-spelt variable. --------- Co-authored-by: georgebid <91198628+georgebid@users.noreply.github.com> --- .../Media/ImageSharpImageUrlGeneratorTests.cs | 314 +++++++++--------- 1 file changed, 151 insertions(+), 163 deletions(-) diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs index ca3b388e44..40f28322dc 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Media/ImageSharpImageUrlGeneratorTests.cs @@ -7,66 +7,52 @@ using Umbraco.Cms.Imaging.ImageSharp.Media; namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.Common.Media; +/// +/// Contains tests for all parameters for image generation options. +/// [TestFixture] public class ImageSharpImageUrlGeneratorTests { private const string MediaPath = "/media/1005/img_0671.jpg"; - private static readonly ImageUrlGenerationOptions.CropCoordinates s_crop = new(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); - - private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus1 = new(0.96m, 0.80827067669172936m); - private static readonly ImageUrlGenerationOptions.FocalPointPosition s_focus2 = new(0.4275m, 0.41m); - private static readonly ImageSharpImageUrlGenerator s_generator = new(new string[0]); + private static readonly ImageUrlGenerationOptions.CropCoordinates _sCrop = new(0.58729977382575338m, 0.055768992440203169m, 0m, 0.32457553600198386m); + private static readonly ImageUrlGenerationOptions.FocalPointPosition _sFocus = new(0.96m, 0.80827067669172936m); + private static readonly ImageSharpImageUrlGenerator _sGenerator = new(Array.Empty()); + /// + /// Tests that the media path is returned if no options are provided. + /// [Test] - public void GetImageUrl_CropAliasTest() + public void GivenMediaPath_AndNoOptions_ReturnsMediaPath() { - var urlString = - s_generator.GetImageUrl( - new ImageUrlGenerationOptions(MediaPath) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual( - MediaPath + "?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", - urlString); + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath)); + Assert.AreEqual(MediaPath, actual); } + /// + /// Test that if options is null, the generated image URL is also null. + /// [Test] - public void GetImageUrl_WidthHeightTest() + public void GivenNullOptions_ReturnsNull() { - var urlString = - s_generator.GetImageUrl( - new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 200, Height = 300 }); - Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=200&height=300", urlString); + var actual = _sGenerator.GetImageUrl(null); + Assert.IsNull(actual); } + /// + /// Test that if a null image url is given, null is returned. + /// [Test] - public void GetImageUrl_FocalPointTest() + public void GivenNullImageUrl_ReturnsNull() { - var urlString = - s_generator.GetImageUrl( - new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus1, Width = 100, Height = 100 }); - Assert.AreEqual(MediaPath + "?rxy=0.96,0.80827067669172936&width=100&height=100", urlString); - } - - [Test] - public void GetImageUrlFurtherOptionsTest() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) - { - FocalPoint = s_focus1, - Width = 200, - Height = 300, - FurtherOptions = "&filter=comic&roundedcorners=radius-26|bgcolor-fff", - }); - Assert.AreEqual( - MediaPath + - "?rxy=0.96,0.80827067669172936&width=200&height=300&filter=comic&roundedcorners=radius-26%7Cbgcolor-fff", - urlString); + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(null)); + Assert.IsNull(actual); } [Test] public void GetImageUrlFurtherOptionsModeAndQualityTest() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) + var urlString = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Quality = 10, FurtherOptions = "format=webp", @@ -80,7 +66,7 @@ public class ImageSharpImageUrlGeneratorTests [Test] public void GetImageUrlFurtherOptionsWithModeAndQualityTest() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) + var urlString = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { FurtherOptions = "quality=10&format=webp", }); @@ -91,169 +77,171 @@ public class ImageSharpImageUrlGeneratorTests } /// - /// Test that if options is null, the generated image URL is also null. + /// Test that if an empty string image url is given, null is returned. /// [Test] - public void GetImageUrlNullOptionsTest() + public void GivenEmptyStringImageUrl_ReturnsEmptyString() { - var urlString = s_generator.GetImageUrl(null); - Assert.AreEqual(null, urlString); + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty)); + Assert.AreEqual(actual, string.Empty); } /// - /// Test that if the image URL is null, the generated image URL is also null. + /// Tests the correct query string is returned when given a crop. /// [Test] - public void GetImageUrlNullTest() + public void GivenCrop_ReturnsExpectedQueryString() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(null)); - Assert.AreEqual(null, urlString); + const string expected = "?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386"; + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) { Crop = _sCrop }); + Assert.AreEqual(expected, actual); } /// - /// Test that if the image URL is empty, the generated image URL is empty. + /// Tests the correct query string is returned when given a width. /// [Test] - public void GetImageUrlEmptyTest() + public void GivenWidth_ReturnsExpectedQueryString() { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty)); - Assert.AreEqual(string.Empty, urlString); + const string expected = "?width=200"; + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) { Width = 200 }); + Assert.AreEqual(expected, actual); } /// - /// Test the GetImageUrl method on the ImageCropDataSet Model + /// Tests the correct query string is returned when given a height. /// [Test] - public void GetBaseCropUrlFromModelTest() + public void GivenHeight_ReturnsExpectedQueryString() { - var urlString = - s_generator.GetImageUrl( - new ImageUrlGenerationOptions(string.Empty) { Crop = s_crop, Width = 100, Height = 100 }); - Assert.AreEqual( - "?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&width=100&height=100", - urlString); + const string expected = "?height=200"; + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) { Height = 200 }); + Assert.AreEqual(expected, actual); } /// - /// Test that if Crop mode is specified as anything other than Crop the image doesn't use the crop + /// Tests the correct query string is returned when provided a focal point. /// [Test] - public void GetImageUrl_SpecifiedCropModeTest() + public void GivenFocalPoint_ReturnsExpectedQueryString() { - var urlStringMin = - s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) - { - ImageCropMode = ImageCropMode.Min, - Width = 300, - Height = 150, - }); - var urlStringBoxPad = - s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) - { - ImageCropMode = ImageCropMode.BoxPad, - Width = 300, - Height = 150, - }); - var urlStringPad = - s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) - { - ImageCropMode = ImageCropMode.Pad, - Width = 300, - Height = 150, - }); - var urlStringMax = - s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) - { - ImageCropMode = ImageCropMode.Max, - Width = 300, - Height = 150, - }); - var urlStringStretch = - s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) - { - ImageCropMode = ImageCropMode.Stretch, - Width = 300, - Height = 150, - }); - - Assert.AreEqual(MediaPath + "?rmode=min&width=300&height=150", urlStringMin); - Assert.AreEqual(MediaPath + "?rmode=boxpad&width=300&height=150", urlStringBoxPad); - Assert.AreEqual(MediaPath + "?rmode=pad&width=300&height=150", urlStringPad); - Assert.AreEqual(MediaPath + "?rmode=max&width=300&height=150", urlStringMax); - Assert.AreEqual(MediaPath + "?rmode=stretch&width=300&height=150", urlStringStretch); + const string expected = "?rxy=0.96,0.80827067669172936"; + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) { FocalPoint = _sFocus }); + Assert.AreEqual(expected, actual); } /// - /// Test for upload property type + /// Tests the correct query string is returned when given further options. + /// There are a few edge case inputs here to ensure thorough testing in future versions. /// - [Test] - public void GetImageUrl_UploadTypeTest() + [TestCase("&filter=comic&roundedcorners=radius-26%7Cbgcolor-fff", "?filter=comic&roundedcorners=radius-26%7Cbgcolor-fff")] + [TestCase("testoptions", "?testoptions=")] + [TestCase("&&&should=strip", "?should=strip")] + [TestCase("should=encode&$^%()", "?should=encode&$%5E%25()=")] + public void GivenFurtherOptions_ReturnsExpectedQueryString(string input, string expected) { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) { - ImageCropMode = ImageCropMode.Crop, - ImageCropAnchor = ImageCropAnchor.Center, - Width = 100, - Height = 270, + FurtherOptions = input, }); - Assert.AreEqual(MediaPath + "?rmode=crop&ranchor=center&width=100&height=270", urlString); + Assert.AreEqual(expected, actual); } /// - /// Test for preferFocalPoint when focal point is centered + /// Test that the correct query string is returned for all image crop modes. /// - [Test] - public void GetImageUrl_PreferFocalPointCenter() + [TestCase(ImageCropMode.Min, "?rmode=min")] + [TestCase(ImageCropMode.BoxPad, "?rmode=boxpad")] + [TestCase(ImageCropMode.Pad, "?rmode=pad")] + [TestCase(ImageCropMode.Max, "?rmode=max")] + [TestCase(ImageCropMode.Stretch, "?rmode=stretch")] + public void GivenCropMode_ReturnsExpectedQueryString(ImageCropMode cropMode, string expectedQueryString) { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 300, Height = 150 }); - Assert.AreEqual(MediaPath + "?width=300&height=150", urlString); - } - - /// - /// Test to check if crop ratio is ignored if useCropDimensions is true - /// - [Test] - public void GetImageUrl_PreDefinedCropNoCoordinatesWithWidthAndFocalPointIgnore() - { - var urlString = - s_generator.GetImageUrl( - new ImageUrlGenerationOptions(MediaPath) { FocalPoint = s_focus2, Width = 270, Height = 161 }); - Assert.AreEqual(MediaPath + "?rxy=0.4275,0.41&width=270&height=161", urlString); - } - - /// - /// Test to check result when only a width parameter is passed, effectivly a resize only - /// - [Test] - public void GetImageUrl_WidthOnlyParameter() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Width = 200 }); - Assert.AreEqual(MediaPath + "?width=200", urlString); - } - - /// - /// Test to check result when only a height parameter is passed, effectivly a resize only - /// - [Test] - public void GetImageUrl_HeightOnlyParameter() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) { Height = 200 }); - Assert.AreEqual(MediaPath + "?height=200", urlString); - } - - /// - /// Test to check result when using a background color with padding - /// - [Test] - public void GetImageUrl_BackgroundColorParameter() - { - var urlString = s_generator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) + var cropUrl = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) { - ImageCropMode = ImageCropMode.Pad, - Width = 400, - Height = 400, - FurtherOptions = "&bgcolor=fff", + ImageCropMode = cropMode, }); - Assert.AreEqual(MediaPath + "?rmode=pad&width=400&height=400&bgcolor=fff", urlString); + + Assert.AreEqual(expectedQueryString, cropUrl); + } + + /// + /// Test that the correct query string is returned for all image crop anchors. + /// + [TestCase(ImageCropAnchor.Bottom, "?ranchor=bottom")] + [TestCase(ImageCropAnchor.BottomLeft, "?ranchor=bottomleft")] + [TestCase(ImageCropAnchor.BottomRight, "?ranchor=bottomright")] + [TestCase(ImageCropAnchor.Center, "?ranchor=center")] + [TestCase(ImageCropAnchor.Left, "?ranchor=left")] + [TestCase(ImageCropAnchor.Right, "?ranchor=right")] + [TestCase(ImageCropAnchor.Top, "?ranchor=top")] + [TestCase(ImageCropAnchor.TopLeft, "?ranchor=topleft")] + [TestCase(ImageCropAnchor.TopRight, "?ranchor=topright")] + public void GivenCropAnchor_ReturnsExpectedQueryString(ImageCropAnchor imageCropAnchor, string expectedQueryString) + { + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) + { + ImageCropAnchor = imageCropAnchor, + }); + Assert.AreEqual(expectedQueryString, actual); + } + + /// + /// Tests that the quality query string always returns the input number regardless of value. + /// + [TestCase(int.MinValue)] + [TestCase(-50)] + [TestCase(0)] + [TestCase(50)] + [TestCase(int.MaxValue)] + public void GivenQuality_ReturnsExpectedQueryString(int quality) + { + var expected = "?quality=" + quality; + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) + { + Quality = quality, + }); + Assert.AreEqual(expected, actual); + } + + /// + /// Tests that the correct query string is returned for cache buster. + /// There are some edge case tests here to ensure thorough testing in future versions. + /// + [TestCase("test-buster", "?rnd=test-buster")] + [TestCase("test-buster&&^-value", "?rnd=test-buster%26%26%5E-value")] + public void GivenCacheBusterValue_ReturnsExpectedQueryString(string input, string expected) + { + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(string.Empty) + { + CacheBusterValue = input, + }); + Assert.AreEqual(expected, actual); + } + + /// + /// Tests that an expected query string is returned when all options are given. + /// This will be a good test to see if something breaks with ordering of query string parameters. + /// + [Test] + public void GivenAllOptions_ReturnsExpectedQueryString() + { + const string expected = + "/media/1005/img_0671.jpg?cc=0.58729977382575338,0.055768992440203169,0,0.32457553600198386&rxy=0.96,0.80827067669172936&rmode=stretch&ranchor=right&width=200&height=200&quality=50&more=options&rnd=buster"; + + var actual = _sGenerator.GetImageUrl(new ImageUrlGenerationOptions(MediaPath) + { + Quality = 50, + Crop = _sCrop, + FocalPoint = _sFocus, + CacheBusterValue = "buster", + FurtherOptions = "more=options", + Height = 200, + Width = 200, + ImageCropAnchor = ImageCropAnchor.Right, + ImageCropMode = ImageCropMode.Stretch, + }); + + Assert.AreEqual(expected, actual); } } From 99268f6211c643f947de9e9326318cd716e15d62 Mon Sep 17 00:00:00 2001 From: LandLogic IT Date: Wed, 22 Mar 2023 17:05:07 +0100 Subject: [PATCH 03/35] Add it key translation --- src/Umbraco.Core/EmbeddedResources/Lang/it.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml index 2430542e66..5e45248d48 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml @@ -2660,6 +2660,7 @@ Per gestire il tuo sito web, è sufficiente aprire il backoffice di Umbraco e in Usato nei tipi di membro Non ci sono riferimenti a tipi di membro. Usato da + Correlato ai seguenti elementi Usato nei documenti Usato nei membri Usato nei media From e550a4ce3acfeffe1e7d08568df1a401a60adb11 Mon Sep 17 00:00:00 2001 From: patrickdemooij9 Date: Thu, 23 Mar 2023 21:11:59 +0100 Subject: [PATCH 04/35] Performance improvement for ReplaceFirst function (#13018) * Some benchmarks for replacefirst & span method --- .../Extensions/StringExtensions.cs | 5 +- .../StringReplaceFirstBenchmarks.cs | 66 +++++++++++++++++++ .../StringReplaceManyBenchmarks.cs | 1 + .../StringExtensionsTests.cs | 8 +++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 tests/Umbraco.Tests.Benchmarks/StringReplaceFirstBenchmarks.cs diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 6a76650523..c1abeb8650 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -1040,14 +1040,15 @@ public static class StringExtensions throw new ArgumentNullException(nameof(text)); } - var pos = text.IndexOf(search, StringComparison.InvariantCulture); + ReadOnlySpan spanText = text.AsSpan(); + var pos = spanText.IndexOf(search, StringComparison.InvariantCulture); if (pos < 0) { return text; } - return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); + return string.Concat(spanText[..pos], replace.AsSpan(), spanText[(pos + search.Length)..]); } /// diff --git a/tests/Umbraco.Tests.Benchmarks/StringReplaceFirstBenchmarks.cs b/tests/Umbraco.Tests.Benchmarks/StringReplaceFirstBenchmarks.cs new file mode 100644 index 0000000000..e5c6c6b2b5 --- /dev/null +++ b/tests/Umbraco.Tests.Benchmarks/StringReplaceFirstBenchmarks.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Umbraco.Tests.Benchmarks.Config; + +namespace Umbraco.Tests.Benchmarks; + +[QuickRunWithMemoryDiagnoserConfig] +public class StringReplaceFirstBenchmarks +{ + [Params("Test string", + "This is a test string that contains multiple test entries", + "This is a string where the searched value is very far back. The system needs to go through all of this code before it reaches the test")] + public string Text { get; set; } + public string Search { get; set; } + public string Replace { get; set; } + + [GlobalSetup] + public void Setup() + { + Search = "test"; + Replace = "release"; + } + + [Benchmark(Baseline = true, Description = "Replace first w/ substring")] + public string SubstringReplaceFirst() + { + var pos = Text.IndexOf(Search, StringComparison.InvariantCulture); + + if (pos < 0) + { + return Text; + } + + return Text.Substring(0, pos) + Replace + Text.Substring(pos + Search.Length); + } + + [Benchmark(Description = "Replace first w/ span")] + public string SpanReplaceFirst() + { + var spanText = Text.AsSpan(); + var pos = spanText.IndexOf(Search, StringComparison.InvariantCulture); + + if (pos < 0) + { + return Text; + } + + return string.Concat(spanText[..pos], Replace.AsSpan(), spanText[(pos + Search.Length)..]); + } + + //| Method | Text | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Allocated | + //|----------------------------- |--------------------- |----------:|---------:|---------:|------:|--------:|-------:|----------:| + //| 'Replace first w/ substring' | Test string | 46.08 ns | 25.83 ns | 1.416 ns | 1.00 | 0.00 | - | - | + //| 'Replace first w/ span' | Test string | 38.59 ns | 19.46 ns | 1.067 ns | 0.84 | 0.05 | - | - | + //| | | | | | | | | | + //| 'Replace first w/ substring' | This(...)test[134] | 407.89 ns | 52.08 ns | 2.855 ns | 1.00 | 0.00 | 0.1833 | 584 B | + //| 'Replace first w/ span' | This(...)test[134] | 372.99 ns | 58.38 ns | 3.200 ns | 0.91 | 0.01 | 0.0941 | 296 B | + //| | | | | | | | | | + //| 'Replace first w/ substring' | This(...)tries[57] | 113.16 ns | 27.95 ns | 1.532 ns | 1.00 | 0.00 | 0.0961 | 304 B | + //| 'Replace first w/ span' | This(...)tries[57] | 76.57 ns | 17.86 ns | 0.979 ns | 0.68 | 0.01 | 0.0455 | 144 B | +} diff --git a/tests/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs b/tests/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs index 096d591463..8c4914d0df 100644 --- a/tests/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs +++ b/tests/Umbraco.Tests.Benchmarks/StringReplaceManyBenchmarks.cs @@ -74,6 +74,7 @@ public class StringReplaceManyBenchmarks return result; } + /* short text, short replacement: diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs index 01fc57c1d8..bd02bead1c 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/ShortStringHelper/StringExtensionsTests.cs @@ -326,6 +326,14 @@ public class StringExtensionsTests Assert.AreEqual(expected, output); } + [TestCase("test to test", "test", "release", "release to test")] + [TestCase("nothing to do", "test", "release", "nothing to do")] + public void ReplaceFirst(string input, string search, string replacement, string expected) + { + var output = input.ReplaceFirst(search, replacement); + Assert.AreEqual(expected, output); + } + [Test] public void IsFullPath() { From 3ee9bfbc93fb82a20f27df05e5c0195cd72291b6 Mon Sep 17 00:00:00 2001 From: Patrick de Mooij Date: Mon, 15 Aug 2022 21:17:43 +0200 Subject: [PATCH 05/35] 12748: Make sure to set writerId --- src/Umbraco.Core/Services/MediaService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 325677407e..fadc682d16 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -728,6 +728,8 @@ namespace Umbraco.Cms.Core.Services media.CreatorId = userId; } + media.WriterId = userId; + _mediaRepository.Save(media); scope.Notifications.Publish(new MediaSavedNotification(media, eventMessages).WithStateFrom(savingNotification)); // TODO: See note about suppressing events in content service From 93a85a46f32aa80ffd9fa1387ce8025b2264b470 Mon Sep 17 00:00:00 2001 From: Ealse Bouma Date: Mon, 22 Aug 2022 10:32:55 +0200 Subject: [PATCH 06/35] Fix: Infinite Editor creates each save action a new version when content is invalid (#12713) --- .../src/common/services/contenteditinghelper.service.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index e7ecb5c93c..30afe39884 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -142,6 +142,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt //update editor state to what is current editorState.set(args.content); + //needs to be manually set for infinite editing mode + args.scope.isNew = args.content.id === 0 && args.scope.isNew; + return $q.reject(err); }); } From b34a93ed596539d791da42821306efc5fa42708e Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Mon, 27 Mar 2023 12:40:39 +0200 Subject: [PATCH 07/35] Prevent overflowing property values (#12943) --- .../src/less/components/umb-node-preview.less | 4 ++-- .../src/less/components/umb-readonlyvalue.less | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index bac1ebc4f3..7e42d0e46e 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -7,7 +7,8 @@ } .umb-editor-wrapper .umb-node-preview { - .umb-property-editor--limit-width(); + word-break: break-word; + .umb-property-editor--limit-width(); } .umb-node-preview:last-of-type { @@ -38,7 +39,6 @@ .umb-node-preview__content { flex: 1 1 auto; - margin-right: 25px; overflow: hidden; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less index 0790bdd07a..f0a910b278 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-readonlyvalue.less @@ -1,3 +1,5 @@ -.umb-readonlyvalue { - position:relative; +.umb-readonlyvalue { + position: relative; + word-break: break-word; + .umb-property-editor--limit-width(); } From a88d0c0ec925a7bc9206c2b6f8923ea67ae58f05 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Mon, 27 Mar 2023 17:46:13 +0200 Subject: [PATCH 08/35] Unit tests need updating now that the WriterId is properly being set after merging #12843 --- .../Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs index 08dddf715d..e9c4ac320b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Packaging/CreatedPackagesRepositoryTests.cs @@ -255,7 +255,7 @@ public class CreatedPackagesRepositoryTests : UmbracoIntegrationTest Assert.AreEqual(test, mediaEntry.Name); Assert.IsNotNull(zipArchive.GetEntry("package.xml")); Assert.AreEqual( - $"", + $"", packageXml.Element("umbPackage").Element("MediaItems").ToString(SaveOptions.DisableFormatting)); Assert.AreEqual(2, zipArchive.Entries.Count()); Assert.AreEqual(ZipArchiveMode.Read, zipArchive.Mode); From 1f6307944b28c5be9717828183de9b04def58c2f Mon Sep 17 00:00:00 2001 From: gadgetfbi Date: Tue, 28 Mar 2023 08:36:53 +0100 Subject: [PATCH 09/35] Fixes: listitem: `
  • ` elements must be contained in a `