Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V14 Bugfix ensures correct line endings for partial view snippets #15906

Merged
merged 4 commits into from Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
50 changes: 50 additions & 0 deletions src/Umbraco.Core/Extensions/LineEndingsExtensions.cs
@@ -0,0 +1,50 @@
using System.Runtime.InteropServices;

namespace Umbraco.Cms.Core.Extensions;

public static class LineEndingsExtensions
{
/// <summary>
/// Ensures Lf only everywhere.
/// </summary>
/// <param name="text">The text to filter.</param>
/// <returns>The filtered text.</returns>
private static string Lf(string text)
{
if (string.IsNullOrEmpty(text))
{
return text;
}

text = text.Replace("\r", string.Empty); // remove CR
return text;
}

/// <summary>
/// Ensures CrLf everywhere.
/// </summary>
/// <param name="text">The text to filter.</param>
/// <returns>The filtered text.</returns>
private static string CrLf(string text)
{
if (string.IsNullOrEmpty(text))
{
return text;
}

text = text.Replace("\r", string.Empty); // remove CR
text = text.Replace("\n", "\r\n"); // add CRLF everywhere
return text;
}

/// <summary>
/// Ensures native line endings.
/// </summary>
/// <param name="text">the text to ensure native line endings for.</param>
/// <returns>the text with native line endings.</returns>
public static string EnsureNativeLineEndings(this string text)
{
var useCrLf = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
return useCrLf ? CrLf(text) : Lf(text);
}
}
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Extensions;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;

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

// Strip the @inherits if it's there
Regex headerMatch = HeaderRegex();
Expand Down
Expand Up @@ -33,9 +33,10 @@ test.describe('Partial View tests', () => {
await expect(umbracoUi.partialView.checkItemNameUnderPartialViewTree(partialViewFileName)).toBeVisible();
})

test.skip('can create a partial view from snippet', async ({umbracoApi, umbracoUi}) => {
test('can create a partial view from snippet', async ({umbracoApi, umbracoUi}) => {
// Arrange
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}';
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}';
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}';

// Act
await umbracoUi.partialView.clickActionsMenuAtRoot();
Expand All @@ -50,8 +51,18 @@ test.describe('Partial View tests', () => {
await umbracoUi.partialView.isSuccessNotificationVisible();
expect(await umbracoApi.partialView.doesExist(partialViewFileName)).toBeTruthy();
const partialViewData = await umbracoApi.partialView.getByName(partialViewFileName);
expect(partialViewData.content.length).toBe(expectedPartialViewContent.length);
expect(partialViewData.content).toBe(expectedPartialViewContent);

switch (process.platform) {
case 'win32':
expect(partialViewData.content).toBe(expectedPartialViewContentWindows);
break;
case 'linux':
expect(partialViewData.content).toBe(expectedPartialViewContentLinux);
break;
default:
throw new Error(`Untested platform: ${process.platform}`);
}

// Verify the new partial view is displayed under the Partial Views section
await umbracoUi.partialView.clickRootFolderCaretButton();
await expect(umbracoUi.partialView.checkItemNameUnderPartialViewTree(partialViewFileName)).toBeVisible();
Expand Down