Merge branch 'main' into v17/dev

This commit is contained in:
Jacob Overgaard
2025-05-16 16:58:11 +02:00
312 changed files with 5502 additions and 3958 deletions

View File

@@ -1,9 +1,9 @@
# Umbraco Acceptance Tests
You can watch a video following these instructions [here](https://www.youtube.com/watch?v=N4hBKB0U-d8) and a longer UmbraCollab recording [here](https://www.youtube.com/watch?v=hvoI28s_fDI). Make sure to use the latest recommended contribution branch rather than v10 that's mentioned in the video. Alternatively, follow along the instructions below.
You can watch a video following these instructions [here](https://www.youtube.com/watch?v=N4hBKB0U-d8) and a longer UmbraCollab recording [here](https://www.youtube.com/watch?v=hvoI28s_fDI). Make sure to use the latest recommended `main` branch rather than v10 that's mentioned in the video. Alternatively, follow along the instructions below.
### Prerequisites
- NodeJS 16+
- NodeJS 22+
- A running installed Umbraco on url: [https://localhost:44339](https://localhost:44339) (Default development port)
- Install using a `SqlServer`/`LocalDb` as the tests execute too fast for `Sqlite` to handle.

View File

@@ -8,7 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@umbraco/json-models-builders": "^2.0.33",
"@umbraco/playwright-testhelpers": "^16.0.9",
"@umbraco/playwright-testhelpers": "^16.0.11",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -66,9 +66,10 @@
}
},
"node_modules/@umbraco/playwright-testhelpers": {
"version": "16.0.9",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.9.tgz",
"integrity": "sha512-nfoRZNYrD2PP6k/GljiINCEA8VM6uvOAlqmkhYOdiTzrgLmVRqZExsNskm1BhlcxDhE6+XZlpjTcFIotFBKLFQ==",
"version": "16.0.11",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.11.tgz",
"integrity": "sha512-jduJC8xqtqQ78Sata3GhafDLavRv0ZaKHKFwz3KdLw0VmLNxgDMABAV0SMFGU9sABGzi3MjEUeVQ4ntH1nvA3w==",
"license": "MIT",
"dependencies": {
"@umbraco/json-models-builders": "2.0.33",
"node-fetch": "^2.6.7"

View File

@@ -21,7 +21,7 @@
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.33",
"@umbraco/playwright-testhelpers": "^16.0.9",
"@umbraco/playwright-testhelpers": "^16.0.11",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"

View File

@@ -0,0 +1,203 @@
import {ConstantHelper, NotificationConstantHelper, test} from '@umbraco/playwright-testhelpers';
import {expect} from "@playwright/test";
let documentTypeId = '';
let childDocumentTypeId = '';
let contentId = '';
let dataTypeId = '';
const contentName = 'TestContent';
const childContentName = 'ChildContent';
const documentTypeName = 'DocumentTypeForContent';
const childDocumentTypeName = 'ChildDocumentType';
const dataTypeName = 'Textstring';
const contentText = 'This is test content text';
const defaultLanguage = 'English (United States)';
const danishLanguage = 'Danish';
test.beforeEach(async ({umbracoApi}) => {
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName);
const dataTypeData = await umbracoApi.dataType.getByName(dataTypeName);
dataTypeId = dataTypeData.id;
await umbracoApi.language.ensureIsoCodeNotExists('da');
await umbracoApi.language.createDanishLanguage();
});
test.afterEach(async ({umbracoApi}) => {
await umbracoApi.language.ensureIsoCodeNotExists('da');
await umbracoApi.document.ensureNameNotExists(contentName);
await umbracoApi.documentType.ensureNameNotExists(childDocumentTypeName);
await umbracoApi.documentType.ensureNameNotExists(documentTypeName);
});
test('can publish invariant content with descendants without unpublished content items', async ({umbracoApi, umbracoUi}) => {
// Arrange
childDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(childDocumentTypeName, dataTypeName, dataTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndDataType(documentTypeName, childDocumentTypeId, dataTypeName, dataTypeId);
contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, contentId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickViewMoreOptionsButton();
await umbracoUi.content.clickPublishWithDescendantsButton();
// Verify variant language
await umbracoUi.content.doesDocumentVariantLanguageItemHaveCount(1);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(defaultLanguage);
await umbracoUi.content.clickPublishWithDescendantsModalButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.publishWithDescendants);
await umbracoUi.content.isErrorNotificationVisible(false);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Published');
expect(contentData.values[0].value).toBe(contentText);
const childContentData = await umbracoApi.document.getByName(childContentName);
expect(childContentData.variants[0].state).toBe('Draft');
});
test('can publish invariant content with descendants and include unpublished content items', async ({umbracoApi, umbracoUi}) => {
// Arrange
childDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(childDocumentTypeName, dataTypeName, dataTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndDataType(documentTypeName, childDocumentTypeId, dataTypeName, dataTypeId);
contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, contentId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickViewMoreOptionsButton();
await umbracoUi.content.clickPublishWithDescendantsButton();
// Verify variant language
await umbracoUi.content.doesDocumentVariantLanguageItemHaveCount(1);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(defaultLanguage);
await umbracoUi.content.clickIncludeUnpublishedDescendantsToggle();
await umbracoUi.content.clickPublishWithDescendantsModalButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.publishWithDescendants);
await umbracoUi.content.isErrorNotificationVisible(false);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Published');
expect(contentData.values[0].value).toBe(contentText);
const childContentData = await umbracoApi.document.getByName(childContentName);
expect(childContentData.variants[0].state).toBe('Published');
});
test('can cancel to publish invariant content with descendants', async ({umbracoApi, umbracoUi}) => {
// Arrange
childDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(childDocumentTypeName, dataTypeName, dataTypeId);
documentTypeId = await umbracoApi.documentType.createDocumentTypeWithAllowedChildNodeAndDataType(documentTypeName, childDocumentTypeId, dataTypeName, dataTypeId);
contentId = await umbracoApi.document.createDocumentWithTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, contentId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickViewMoreOptionsButton();
await umbracoUi.content.clickPublishWithDescendantsButton();
// Verify variant language
await umbracoUi.content.doesDocumentVariantLanguageItemHaveCount(1);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(defaultLanguage);
await umbracoUi.content.clickCloseButton();
// Assert
await umbracoUi.content.isErrorNotificationVisible(false);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Draft');
expect(contentData.values[0].value).toBe(contentText);
const childContentData = await umbracoApi.document.getByName(childContentName);
expect(childContentData.variants[0].state).toBe('Draft');
});
test('can publish variant content with descendants without unpublished content items', async ({umbracoApi, umbracoUi}) => {
// Arrange
childDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(childDocumentTypeName, dataTypeName, dataTypeId);
documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithAllowedChildNodeAndInvariantPropertyEditor(documentTypeName, childDocumentTypeId, dataTypeName, dataTypeId);
contentId = await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, contentId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickViewMoreOptionsButton();
await umbracoUi.content.clickPublishWithDescendantsButton();
// Verify variant language
await umbracoUi.content.doesDocumentVariantLanguageItemHaveCount(2);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(defaultLanguage);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(danishLanguage);
await umbracoUi.content.clickPublishWithDescendantsModalButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.publishWithDescendants);
await umbracoUi.content.isErrorNotificationVisible(false);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Published');
expect(contentData.values[0].value).toBe(contentText);
const childContentData = await umbracoApi.document.getByName(childContentName);
expect(childContentData.variants[0].state).toBe('Draft');
});
test('can publish variant content with descendants and include unpublished content items', async ({umbracoApi, umbracoUi}) => {
// Arrange
childDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(childDocumentTypeName, dataTypeName, dataTypeId);
documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithAllowedChildNodeAndInvariantPropertyEditor(documentTypeName, childDocumentTypeId, dataTypeName, dataTypeId);
contentId = await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, contentId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickViewMoreOptionsButton();
await umbracoUi.content.clickPublishWithDescendantsButton();
// Verify variant language
await umbracoUi.content.doesDocumentVariantLanguageItemHaveCount(2);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(defaultLanguage);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(danishLanguage);
await umbracoUi.content.clickIncludeUnpublishedDescendantsToggle();
await umbracoUi.content.clickPublishWithDescendantsModalButton();
// Assert
await umbracoUi.content.doesSuccessNotificationHaveText(NotificationConstantHelper.success.publishWithDescendants);
await umbracoUi.content.isErrorNotificationVisible(false);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Published');
expect(contentData.values[0].value).toBe(contentText);
const childContentData = await umbracoApi.document.getByName(childContentName);
expect(childContentData.variants[0].state).toBe('Published');
});
test('can cancel to publish variant content with descendants', async ({umbracoApi, umbracoUi}) => {
// Arrange
childDocumentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(childDocumentTypeName, dataTypeName, dataTypeId);
documentTypeId = await umbracoApi.documentType.createVariantDocumentTypeWithAllowedChildNodeAndInvariantPropertyEditor(documentTypeName, childDocumentTypeId, dataTypeName, dataTypeId);
contentId = await umbracoApi.document.createDocumentWithEnglishCultureAndTextContent(contentName, documentTypeId, contentText, dataTypeName);
await umbracoApi.document.createDefaultDocumentWithParent(childContentName, childDocumentTypeId, contentId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
// Act
await umbracoUi.content.goToContentWithName(contentName);
await umbracoUi.content.clickViewMoreOptionsButton();
await umbracoUi.content.clickPublishWithDescendantsButton();
// Verify variant language
await umbracoUi.content.doesDocumentVariantLanguageItemHaveCount(2);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(defaultLanguage);
await umbracoUi.content.doesDocumentVariantLanguageItemHaveName(danishLanguage);
await umbracoUi.content.clickCloseButton();
// Assert
await umbracoUi.content.isErrorNotificationVisible(false);
const contentData = await umbracoApi.document.getByName(contentName);
expect(contentData.variants[0].state).toBe('Draft');
expect(contentData.values[0].value).toBe(contentText);
const childContentData = await umbracoApi.document.getByName(childContentName);
expect(childContentData.variants[0].state).toBe('Draft');
});

View File

@@ -59,8 +59,6 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
private IRelationService RelationService => GetRequiredService<IRelationService>();
private ILocalizedTextService TextService => GetRequiredService<ILocalizedTextService>();
private ITagService TagService => GetRequiredService<ITagService>();
private IPublicAccessService PublicAccessService => GetRequiredService<IPublicAccessService>();
@@ -738,8 +736,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
[Test]
public void Can_Unpublish_Content_Variation()
{
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr,
out var contentType);
var content = CreateEnglishAndFrenchDocument(out var langUk, out var langFr, out var contentType);
var saved = ContentService.Save(content);
var published = ContentService.Publish(content, new[] { langFr.IsoCode, langUk.IsoCode });
@@ -1032,7 +1029,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
// audit log will only show that french was published
var lastLog = AuditService.GetLogs(content.Id).Last();
Assert.AreEqual("Published languages: French (France)", lastLog.Comment);
Assert.AreEqual("Published languages: fr-FR", lastLog.Comment);
// re-get
content = ContentService.GetById(content.Id);
@@ -1042,7 +1039,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
// audit log will only show that english was published
lastLog = AuditService.GetLogs(content.Id).Last();
Assert.AreEqual("Published languages: English (United Kingdom)", lastLog.Comment);
Assert.AreEqual("Published languages: en-GB", lastLog.Comment);
}
[Test]
@@ -1079,7 +1076,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
// audit log will only show that french was unpublished
var lastLog = AuditService.GetLogs(content.Id).Last();
Assert.AreEqual("Unpublished languages: French (France)", lastLog.Comment);
Assert.AreEqual("Unpublished languages: fr-FR", lastLog.Comment);
// re-get
content = ContentService.GetById(content.Id);
@@ -1088,7 +1085,7 @@ internal sealed class ContentServiceTests : UmbracoIntegrationTestWithContent
// audit log will only show that english was published
var logs = AuditService.GetLogs(content.Id).ToList();
Assert.AreEqual("Unpublished languages: English (United Kingdom)", logs[^2].Comment);
Assert.AreEqual("Unpublished languages: en-GB", logs[^2].Comment);
Assert.AreEqual("Unpublished (mandatory language unpublished)", logs[^1].Comment);
}

View File

@@ -1,4 +1,3 @@
using System.Linq;
using NUnit.Framework;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Extensions;
@@ -14,10 +13,10 @@ public class RequestHandlerSettingsTests
var settings = new RequestHandlerSettings
{
UserDefinedCharCollection =
{
[
new() { Char = "test", Replacement = "replace" },
new() { Char = "test2", Replacement = "replace2" },
}
]
};
var actual = settings.GetCharReplacements().ToList();
@@ -34,15 +33,15 @@ public class RequestHandlerSettingsTests
var settings = new RequestHandlerSettings
{
UserDefinedCharCollection =
{
[
new() { Char = "test", Replacement = "replace" },
new() { Char = "test2", Replacement = "replace2" },
},
],
EnableDefaultCharReplacements = false,
};
var actual = settings.GetCharReplacements().ToList();
Assert.AreEqual(settings.UserDefinedCharCollection.Count, actual.Count);
Assert.AreEqual(settings.UserDefinedCharCollection.Count(), actual.Count);
Assert.That(actual, Is.EquivalentTo(settings.UserDefinedCharCollection));
}
@@ -52,10 +51,10 @@ public class RequestHandlerSettingsTests
var settings = new RequestHandlerSettings
{
UserDefinedCharCollection =
{
[
new() { Char = "%", Replacement = "percent" },
new() { Char = ".", Replacement = "dot" },
}
]
};
var actual = settings.GetCharReplacements().ToList();
@@ -73,11 +72,11 @@ public class RequestHandlerSettingsTests
var settings = new RequestHandlerSettings
{
UserDefinedCharCollection =
{
[
new() { Char = "%", Replacement = "percent" },
new() { Char = ".", Replacement = "dot" },
new() { Char = "new", Replacement = "new" },
}
]
};
var actual = settings.GetCharReplacements().ToList();