Skip to content

Commit e7f40af

Browse files
authored
V14 Bugfix ensures correct line endings for partial view snippets (#15906)
* Created extension class so we can ensure native line endings * Added usage of extension method for ensuring native line endings * Added tests, to see if the snippets return the correct content * Removed space
1 parent 1e69695 commit e7f40af

File tree

3 files changed

+67
-4
lines changed

3 files changed

+67
-4
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace Umbraco.Cms.Core.Extensions;
4+
5+
public static class LineEndingsExtensions
6+
{
7+
/// <summary>
8+
/// Ensures Lf only everywhere.
9+
/// </summary>
10+
/// <param name="text">The text to filter.</param>
11+
/// <returns>The filtered text.</returns>
12+
private static string Lf(string text)
13+
{
14+
if (string.IsNullOrEmpty(text))
15+
{
16+
return text;
17+
}
18+
19+
text = text.Replace("\r", string.Empty); // remove CR
20+
return text;
21+
}
22+
23+
/// <summary>
24+
/// Ensures CrLf everywhere.
25+
/// </summary>
26+
/// <param name="text">The text to filter.</param>
27+
/// <returns>The filtered text.</returns>
28+
private static string CrLf(string text)
29+
{
30+
if (string.IsNullOrEmpty(text))
31+
{
32+
return text;
33+
}
34+
35+
text = text.Replace("\r", string.Empty); // remove CR
36+
text = text.Replace("\n", "\r\n"); // add CRLF everywhere
37+
return text;
38+
}
39+
40+
/// <summary>
41+
/// Ensures native line endings.
42+
/// </summary>
43+
/// <param name="text">the text to ensure native line endings for.</param>
44+
/// <returns>the text with native line endings.</returns>
45+
public static string EnsureNativeLineEndings(this string text)
46+
{
47+
var useCrLf = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
48+
return useCrLf ? CrLf(text) : Lf(text);
49+
}
50+
}

src/Umbraco.Core/Snippets/PartialViewSnippetCollectionBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using Microsoft.Extensions.FileProviders;
44
using Umbraco.Cms.Core.Composing;
5+
using Umbraco.Cms.Core.Extensions;
56
using Umbraco.Cms.Core.Strings;
67
using Umbraco.Extensions;
78

@@ -38,6 +39,7 @@ protected override IEnumerable<PartialViewSnippet> CreateItems(IServiceProvider
3839
private string CleanUpSnippetContent(string content)
3940
{
4041
const string partialViewHeader = "@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage";
42+
content = content.EnsureNativeLineEndings();
4143

4244
// Strip the @inherits if it's there
4345
Regex headerMatch = HeaderRegex();

tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/PartialView/PartialView.spec.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ test.describe('Partial View tests', () => {
3333
await expect(umbracoUi.partialView.checkItemNameUnderPartialViewTree(partialViewFileName)).toBeVisible();
3434
})
3535

36-
test.skip('can create a partial view from snippet', async ({umbracoApi, umbracoUi}) => {
36+
test('can create a partial view from snippet', async ({umbracoApi, umbracoUi}) => {
3737
// Arrange
38-
const expectedPartialViewContent = '@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage\n@using Umbraco.Cms.Core.Routing\n@using Umbraco.Extensions\n\n@inject IPublishedUrlProvider PublishedUrlProvider\n@*\n This snippet makes a breadcrumb of parents using an unordered HTML list.\n\n How it works:\n - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back\n - Finally it outputs the name of the current page (without a link)\n*@\n\n@{ var selection = Model.Ancestors().ToArray(); }\n\n@if (selection?.Length > 0)\n{\n <ul class=\"breadcrumb\">\n @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@\n @foreach (var item in selection.OrderBy(x => x.Level))\n {\n <li><a href=\"@item.Url(PublishedUrlProvider)\">@item.Name</a> <span class=\"divider\">/</span></li>\n }\n\n @* Display the current page as the last item in the list *@\n <li class=\"active\">@Model.Name</li>\n </ul>\n}';
38+
const expectedPartialViewContentWindows = '@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage\r\n@using Umbraco.Cms.Core.Routing\r\n@using Umbraco.Extensions\r\n\n@inject IPublishedUrlProvider PublishedUrlProvider\r\n@*\r\n This snippet makes a breadcrumb of parents using an unordered HTML list.\r\n\r\n How it works:\r\n - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back\r\n - Finally it outputs the name of the current page (without a link)\r\n*@\r\n\r\n@{ var selection = Model.Ancestors().ToArray(); }\r\n\r\n@if (selection?.Length > 0)\r\n{\r\n <ul class=\"breadcrumb\">\r\n @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@\r\n @foreach (var item in selection.OrderBy(x => x.Level))\r\n {\r\n <li><a href=\"@item.Url(PublishedUrlProvider)\">@item.Name</a> <span class=\"divider\">/</span></li>\r\n }\r\n\r\n @* Display the current page as the last item in the list *@\r\n <li class=\"active\">@Model.Name</li>\r\n </ul>\r\n}';
39+
const expectedPartialViewContentLinux = '@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage\n@using Umbraco.Cms.Core.Routing\n@using Umbraco.Extensions\n\n@inject IPublishedUrlProvider PublishedUrlProvider\n@*\n This snippet makes a breadcrumb of parents using an unordered HTML list.\n\n How it works:\n - It uses the Ancestors() method to get all parents and then generates links so the visitor can go back\n - Finally it outputs the name of the current page (without a link)\n*@\n\n@{ var selection = Model.Ancestors().ToArray(); }\n\n@if (selection?.Length > 0)\n{\n <ul class=\"breadcrumb\">\n @* For each page in the ancestors collection which have been ordered by Level (so we start with the highest top node first) *@\n @foreach (var item in selection.OrderBy(x => x.Level))\n {\n <li><a href=\"@item.Url(PublishedUrlProvider)\">@item.Name</a> <span class=\"divider\">/</span></li>\n }\n\n @* Display the current page as the last item in the list *@\n <li class=\"active\">@Model.Name</li>\n </ul>\n}';
3940

4041
// Act
4142
await umbracoUi.partialView.clickActionsMenuAtRoot();
@@ -50,8 +51,18 @@ test.describe('Partial View tests', () => {
5051
await umbracoUi.partialView.isSuccessNotificationVisible();
5152
expect(await umbracoApi.partialView.doesExist(partialViewFileName)).toBeTruthy();
5253
const partialViewData = await umbracoApi.partialView.getByName(partialViewFileName);
53-
expect(partialViewData.content.length).toBe(expectedPartialViewContent.length);
54-
expect(partialViewData.content).toBe(expectedPartialViewContent);
54+
55+
switch (process.platform) {
56+
case 'win32':
57+
expect(partialViewData.content).toBe(expectedPartialViewContentWindows);
58+
break;
59+
case 'linux':
60+
expect(partialViewData.content).toBe(expectedPartialViewContentLinux);
61+
break;
62+
default:
63+
throw new Error(`Untested platform: ${process.platform}`);
64+
}
65+
5566
// Verify the new partial view is displayed under the Partial Views section
5667
await umbracoUi.partialView.clickRootFolderCaretButton();
5768
await expect(umbracoUi.partialView.checkItemNameUnderPartialViewTree(partialViewFileName)).toBeVisible();

0 commit comments

Comments
 (0)