Merge branch 'release/17.0' into v17/dev

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/entity-action/restore-from-recycle-bin/restore-from-recycle-bin.action.ts
#	tests/Umbraco.Tests.AcceptanceTest/package-lock.json
#	tests/Umbraco.Tests.AcceptanceTest/package.json
This commit is contained in:
leekelleher
2025-10-21 16:11:22 +01:00
170 changed files with 2568 additions and 761 deletions

View File

@@ -8,7 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@umbraco/json-models-builders": "^2.0.40",
"@umbraco/playwright-testhelpers": "^17.0.0-beta.4",
"@umbraco/playwright-testhelpers": "^17.0.0-beta.7",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"
@@ -58,21 +58,21 @@
}
},
"node_modules/@umbraco/json-models-builders": {
"version": "2.0.40",
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.40.tgz",
"integrity": "sha512-Yqojp/0akRgXsnjg18+MjMdkRvFrmlUNbfITgZ3d1h/PIRbWXPNKY1YAfZmdUv+g1SRSHrbIRpPPtSy+gNOjHw==",
"version": "2.0.41",
"resolved": "https://registry.npmjs.org/@umbraco/json-models-builders/-/json-models-builders-2.0.41.tgz",
"integrity": "sha512-rCNUHCOpcuWIj7xUhk0lpcn4jzk9y82jHs9FSb7kxH716AnDyYvwuI+J0Ayd4hhWtXXqNCRqugCNYjG+rvzshQ==",
"license": "MIT",
"dependencies": {
"camelize": "^1.0.1"
}
},
"node_modules/@umbraco/playwright-testhelpers": {
"version": "17.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.0-beta.4.tgz",
"integrity": "sha512-+OE1A2oAdFel4myf5T/jJLuw0aLvSOUBplkUfsYFj2ACeLygfAp/MM7q2RQ+YlCym/wdF+jAqJM3g+zsKEDjaQ==",
"version": "17.0.0-beta.7",
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.0-beta.7.tgz",
"integrity": "sha512-5fhmVVSpJkH6Inx8nA9qqqvZzYuPdDxJdQF2IzY0oSf8C0eti+TJ2BKrYfTLmZTfVqmHUas72BMGser5pfpl9A==",
"license": "MIT",
"dependencies": {
"@umbraco/json-models-builders": "2.0.40",
"@umbraco/json-models-builders": "2.0.41",
"node-fetch": "^2.6.7"
}
},

View File

@@ -22,7 +22,7 @@
},
"dependencies": {
"@umbraco/json-models-builders": "^2.0.40",
"@umbraco/playwright-testhelpers": "^17.0.0-beta.4",
"@umbraco/playwright-testhelpers": "^17.0.0-beta.7",
"camelize": "^1.0.0",
"dotenv": "^16.3.1",
"node-fetch": "^2.6.7"

View File

@@ -51,7 +51,7 @@ test('cannot create child content if allowed child node is disabled', async ({um
// Assert
await umbracoUi.content.isDocumentTypeNameVisible(documentTypeName, false);
await umbracoUi.content.doesModalHaveText(noAllowedDocumentTypeAvailableMessage);
await umbracoUi.content.doesDocumentModalHaveText(noAllowedDocumentTypeAvailableMessage);
});
test('can create multiple child nodes with different document types', async ({umbracoApi, umbracoUi}) => {

View File

@@ -51,7 +51,7 @@ test('can create content using an invariant document blueprint', async ({umbraco
await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType(documentTypeName);
await umbracoUi.content.clickModalMenuItemWithName(documentBlueprintName);
await umbracoUi.content.selectDocumentBlueprintWithName(documentBlueprintName);
await umbracoUi.content.clickSaveButtonForContent();
// Assert
@@ -75,7 +75,7 @@ test('can create content using a variant document blueprint', async ({umbracoApi
await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType(documentTypeName);
await umbracoUi.content.clickModalMenuItemWithName(documentBlueprintName);
await umbracoUi.content.selectDocumentBlueprintWithName(documentBlueprintName);
await umbracoUi.content.clickSaveButtonForContent();
await umbracoUi.content.clickSaveButton();
@@ -104,7 +104,7 @@ test('can create content with different name using an invariant document bluepri
await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType(documentTypeName);
await umbracoUi.content.clickModalMenuItemWithName(documentBlueprintName);
await umbracoUi.content.selectDocumentBlueprintWithName(documentBlueprintName);
await umbracoUi.content.enterContentName(contentName);
await umbracoUi.content.clickSaveButtonForContent();
@@ -130,7 +130,7 @@ test('can create content with different name using a variant document blueprint'
await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType(documentTypeName);
await umbracoUi.content.clickModalMenuItemWithName(documentBlueprintName);
await umbracoUi.content.selectDocumentBlueprintWithName(documentBlueprintName);
await umbracoUi.content.enterContentName(contentName);
await umbracoUi.content.clickSaveButtonForContent();
await umbracoUi.content.clickSaveButton();
@@ -161,7 +161,7 @@ test('can create content using a document blueprint with block list', async ({um
await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType(documentTypeName);
await umbracoUi.content.clickModalMenuItemWithName(documentBlueprintName);
await umbracoUi.content.selectDocumentBlueprintWithName(documentBlueprintName);
await umbracoUi.content.clickSaveButtonForContent();
// Assert
@@ -187,7 +187,7 @@ test('can create content using a document blueprint with block grid', async ({um
await umbracoUi.content.clickActionsMenuAtRoot();
await umbracoUi.content.clickCreateActionMenuOption();
await umbracoUi.content.chooseDocumentType(documentTypeName);
await umbracoUi.content.clickModalMenuItemWithName(documentBlueprintName);
await umbracoUi.content.selectDocumentBlueprintWithName(documentBlueprintName);
await umbracoUi.content.clickSaveButtonForContent();
// Assert
@@ -197,4 +197,4 @@ test('can create content using a document blueprint with block grid', async ({um
expect(contentData.values[0].value.contentData[0].values[0].value.markup).toEqual(textContent);
const blockListValue = contentData.values.find(item => item.editorAlias === "Umbraco.BlockGrid")?.value;
expect(blockListValue).toBeTruthy();
});
});

View File

@@ -23,7 +23,9 @@ test('can create a document blueprint from the settings menu', {tag: '@smoke'},
// Act
await umbracoUi.documentBlueprint.clickActionsMenuAtRoot();
await umbracoUi.documentBlueprint.clickCreateActionMenuOption();
await umbracoUi.documentBlueprint.clickCreateNewDocumentBlueprintButton();
await umbracoUi.documentBlueprint.clickTextButtonWithName(documentTypeName);
await umbracoUi.documentBlueprint.clickChooseButton();
await umbracoUi.documentBlueprint.enterDocumentBlueprintName(documentBlueprintName);
await umbracoUi.documentBlueprint.clickSaveButton();
@@ -108,7 +110,9 @@ test('can create a variant document blueprint', {tag: '@release'}, async ({umbra
// Act
await umbracoUi.documentBlueprint.clickActionsMenuAtRoot();
await umbracoUi.documentBlueprint.clickCreateActionMenuOption();
await umbracoUi.documentBlueprint.clickCreateNewDocumentBlueprintButton();
await umbracoUi.documentBlueprint.clickTextButtonWithName(documentTypeName);
await umbracoUi.documentBlueprint.clickChooseButton();
await umbracoUi.documentBlueprint.enterDocumentBlueprintName(documentBlueprintName);
await umbracoUi.documentBlueprint.clickSaveButton();

View File

@@ -173,7 +173,7 @@ test('can remove a header from a webhook', async ({umbracoApi, umbracoUi}) => {
expect(await umbracoApi.webhook.doesWebhookHaveHeader(webhookName, headerName, headerValue)).toBeFalsy();
});
test('cannot add both content event and media event for a webhook', {tag: '@release'}, async ({umbracoApi, umbracoUi}) => {
test('cannot add both content event and media event for a webhook', async ({umbracoApi, umbracoUi}) => {
// Arrange
const event = 'Content Published';
await umbracoApi.webhook.createDefaultWebhook(webhookName, webhookSiteToken, event);
@@ -185,4 +185,4 @@ test('cannot add both content event and media event for a webhook', {tag: '@rele
// Assert
await umbracoUi.webhook.isModalMenuItemWithNameDisabled('Media Saved');
await umbracoUi.webhook.isModalMenuItemWithNameDisabled('Media Deleted');
});
});

View File

@@ -27,7 +27,7 @@ test.afterEach(async ({umbracoApi}) => {
await umbracoApi.media.ensureNameNotExists(mediaName);
});
test('can trigger when content is published', {tag: '@release'}, async ({umbracoApi, umbracoUi}) => {
test('can trigger when content is published', async ({umbracoApi, umbracoUi}) => {
test.slow();
// Arrange

View File

@@ -34,9 +34,7 @@ public abstract class UmbracoIntegrationTestWithContentEditing : UmbracoIntegrat
protected ContentCreateModel Textpage { get; private set; }
protected ContentScheduleCollection ContentSchedule { get; private set; }
protected CultureAndScheduleModel CultureAndSchedule { get; private set; }
protected ICollection<CulturePublishScheduleModel> CultureAndSchedule { get; private set; }
protected int TextpageId { get; private set; }
@@ -91,11 +89,7 @@ public abstract class UmbracoIntegrationTestWithContentEditing : UmbracoIntegrat
}
// Sets the culture and schedule for the content, in this case, we are publishing immediately for all cultures
ContentSchedule = new ContentScheduleCollection();
CultureAndSchedule = new CultureAndScheduleModel
{
CulturesToPublishImmediately = new HashSet<string> { "*" }, Schedules = ContentSchedule,
};
CultureAndSchedule = [new CulturePublishScheduleModel { Culture = "*", Schedule = null }];
// Create and Save Content "Text Page 1" based on "umbTextpage" -> 1054
PublishedTextPage = ContentEditingBuilder.CreateSimpleContent(ContentType.Key, "Published Page");

View File

@@ -35,6 +35,8 @@ public class PublishedContentFallbackTests : UmbracoIntegrationTest
private IApiContentBuilder ApiContentBuilder => GetRequiredService<IApiContentBuilder>();
private ILanguageService LanguageService => GetRequiredService<ILanguageService>();
protected override void CustomTestSetup(IUmbracoBuilder builder)
=> builder
.AddUmbracoHybridCache()
@@ -98,6 +100,36 @@ public class PublishedContentFallbackTests : UmbracoIntegrationTest
Assert.AreEqual(invariantTitle, invariantValue);
}
[TestCase("Danish title", true)]
[TestCase("Danish title", false)]
[TestCase(null, true)]
[TestCase(null, false)]
public async Task Property_Value_Can_Perform_Explicit_Language_Fallback(string? danishTitle, bool performFallbackToDefaultLanguage)
{
var danishLanguage = new Language("da-DK", "Danish")
{
FallbackIsoCode = "en-US"
};
await LanguageService.CreateAsync(danishLanguage, Constants.Security.SuperUserKey);
UmbracoContextFactory.EnsureUmbracoContext();
const string englishTitle = "English title";
var publishedContent = await SetupCultureVariantContentAsync(englishTitle, danishTitle);
VariationContextAccessor.VariationContext = new VariationContext(culture: "da-DK", segment: null);
var danishValue = publishedContent.Value<string>(PublishedValueFallback, "title");
Assert.AreEqual(danishTitle ?? string.Empty, danishValue);
var fallback = performFallbackToDefaultLanguage ? Fallback.ToDefaultLanguage : Fallback.ToLanguage;
var fallbackValue = publishedContent.Value<string>(PublishedValueFallback, "title", fallback: fallback);
Assert.AreEqual(danishTitle ?? englishTitle, fallbackValue);
VariationContextAccessor.VariationContext = new VariationContext(culture: "en-US", segment: null);
var englishValue = publishedContent.Value<string>(PublishedValueFallback, "title");
Assert.AreEqual(englishTitle, englishValue);
}
private async Task<IPublishedContent> SetupSegmentedContentAsync(string? invariantTitle, string? segmentedTitle)
{
var contentType = new ContentTypeBuilder()
@@ -124,11 +156,47 @@ public class PublishedContentFallbackTests : UmbracoIntegrationTest
ContentService.Save(content);
ContentService.Publish(content, ["*"]);
return GetPublishedContent(content.Key);
}
private async Task<IPublishedContent> SetupCultureVariantContentAsync(string englishTitle, string? danishTitle)
{
var contentType = new ContentTypeBuilder()
.WithAlias("theContentType")
.WithContentVariation(ContentVariation.Culture)
.AddPropertyType()
.WithAlias("title")
.WithName("Title")
.WithDataTypeId(Constants.DataTypes.Textbox)
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox)
.WithValueStorageType(ValueStorageType.Nvarchar)
.WithVariations(ContentVariation.Culture)
.Done()
.WithAllowAsRoot(true)
.Build();
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
var content = new ContentBuilder()
.WithContentType(contentType)
.WithCultureName("en-US", "EN")
.WithCultureName("da-DK", "DA")
.WithName("Content")
.Build();
content.SetValue("title", englishTitle, culture: "en-US");
content.SetValue("title", danishTitle, culture: "da-DK");
ContentService.Save(content);
ContentService.Publish(content, ["en-US", "da-DK"]);
return GetPublishedContent(content.Key);
}
private IPublishedContent GetPublishedContent(Guid key)
{
ContentCacheRefresher.Refresh([new ContentCacheRefresher.JsonPayload { ChangeTypes = TreeChangeTypes.RefreshAll }]);
UmbracoContextAccessor.Clear();
var umbracoContext = UmbracoContextFactory.EnsureUmbracoContext().UmbracoContext;
var publishedContent = umbracoContext.Content.GetById(content.Key);
var publishedContent = umbracoContext.Content.GetById(key);
Assert.IsNotNull(publishedContent);
return publishedContent;

View File

@@ -1932,4 +1932,95 @@ internal partial class BlockListElementLevelVariationTests
Assert.AreEqual(expectedPickedContent.Key, actualPickedPublishedContent.Key);
}
}
[TestCase(ContentVariation.Culture, false)]
[TestCase(ContentVariation.Culture, true)]
[TestCase(ContentVariation.Nothing, false)]
[TestCase(ContentVariation.Nothing, true)]
public async Task Can_Perform_Language_Fallback(ContentVariation elementTypeVariation, bool performFallbackToDefaultLanguage)
{
var daDkLanguage = await LanguageService.GetAsync("da-DK");
Assert.IsNotNull(daDkLanguage);
daDkLanguage.FallbackIsoCode = "en-US";
var saveLanguageResult = await LanguageService.UpdateAsync(daDkLanguage, Constants.Security.SuperUserKey);
Assert.IsTrue(saveLanguageResult.Success);
daDkLanguage = await LanguageService.GetAsync("da-DK");
Assert.AreEqual("en-US", daDkLanguage?.FallbackIsoCode);
var elementType = CreateElementType(elementTypeVariation);
var blockListDataType = await CreateBlockListDataType(elementType);
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType, ContentVariation.Culture);
var content = CreateContent(
contentType,
elementType,
new []
{
new BlockProperty(
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "English invariantText content value" },
new() { Alias = "variantText", Value = "English variantText content value" }
},
new List<BlockPropertyValue>
{
new() { Alias = "invariantText", Value = "English invariantText settings value" },
new() { Alias = "variantText", Value = "English variantText settings value" }
},
"en-US",
null)
},
true);
AssertPropertyValuesWithFallback("en-US",
"English invariantText content value", "English variantText content value",
"English invariantText settings value", "English variantText settings value");
AssetEmptyPropertyValues("da-DK");
AssertPropertyValuesWithFallback("da-DK",
"English invariantText content value", "English variantText content value",
"English invariantText settings value", "English variantText settings value");
void AssertPropertyValuesWithFallback(string culture,
string expectedInvariantContentValue, string expectedVariantContentValue,
string expectedInvariantSettingsValue, string expectedVariantSettingsValue)
{
SetVariationContext(culture, null);
var publishedContent = GetPublishedContent(content.Key);
var fallback = performFallbackToDefaultLanguage ? Fallback.ToDefaultLanguage : Fallback.ToLanguage;
var publishedValueFallback = GetRequiredService<IPublishedValueFallback>();
var value = publishedContent.Value<BlockListModel>(publishedValueFallback, "blocks", fallback: fallback);
Assert.IsNotNull(value);
Assert.AreEqual(1, value.Count);
var blockListItem = value.First();
Assert.AreEqual(2, blockListItem.Content.Properties.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(expectedInvariantContentValue, blockListItem.Content.Value<string>("invariantText"));
Assert.AreEqual(expectedVariantContentValue, blockListItem.Content.Value<string>("variantText"));
});
Assert.AreEqual(2, blockListItem.Settings.Properties.Count());
Assert.Multiple(() =>
{
Assert.AreEqual(expectedInvariantSettingsValue, blockListItem.Settings.Value<string>("invariantText"));
Assert.AreEqual(expectedVariantSettingsValue, blockListItem.Settings.Value<string>("variantText"));
});
}
void AssetEmptyPropertyValues(string culture)
{
SetVariationContext(culture, null);
var publishedContent = GetPublishedContent(content.Key);
var value = publishedContent.Value<BlockListModel>("blocks");
Assert.NotNull(value);
Assert.IsEmpty(value);
}
}
}

View File

@@ -8,7 +8,6 @@ using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Infrastructure.HybridCache;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Builders.Extensions;
using Umbraco.Cms.Tests.Common.Testing;
@@ -39,10 +38,10 @@ public class DateTimePropertyEditorTests : UmbracoIntegrationTest
private static readonly object[] _sourceList1 =
[
new object[] { Constants.PropertyEditors.Aliases.DateOnly, false, new DateOnly(2025, 1, 22) },
new object[] { Constants.PropertyEditors.Aliases.DateOnly, false, new DateOnly(2025, 6, 22) },
new object[] { Constants.PropertyEditors.Aliases.TimeOnly, false, new TimeOnly(18, 33, 1) },
new object[] { Constants.PropertyEditors.Aliases.DateTimeUnspecified, false, new DateTime(2025, 1, 22, 18, 33, 1) },
new object[] { Constants.PropertyEditors.Aliases.DateTimeWithTimeZone, true, new DateTimeOffset(2025, 1, 22, 18, 33, 1, TimeSpan.Zero) },
new object[] { Constants.PropertyEditors.Aliases.DateTimeUnspecified, false, new DateTime(2025, 6, 22, 18, 33, 1) },
new object[] { Constants.PropertyEditors.Aliases.DateTimeWithTimeZone, true, new DateTimeOffset(2025, 6, 22, 18, 33, 1, TimeSpan.FromHours(2)) },
];
[TestCaseSource(nameof(_sourceList1))]
@@ -106,7 +105,7 @@ public class DateTimePropertyEditorTests : UmbracoIntegrationTest
.WithValue(
new JsonObject
{
["date"] = "2025-01-22T18:33:01.0000000+00:00",
["date"] = "2025-06-22T18:33:01.0000000+02:00",
["timeZone"] = "Europe/Copenhagen",
})
.Done()
@@ -127,7 +126,6 @@ public class DateTimePropertyEditorTests : UmbracoIntegrationTest
Assert.IsTrue(publishResult.Success);
var test = ((DocumentCache)PublishedContentCache).GetAtRoot(false);
var publishedContent = await PublishedContentCache.GetByIdAsync(createContentResult.Result.Content.Key, false);
Assert.IsNotNull(publishedContent);

View File

@@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
using Umbraco.Cms.Tests.Common.Builders;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
@@ -26,10 +27,10 @@ internal sealed class DocumentHybridCacheTests : UmbracoIntegrationTestWithConte
private IPublishedContentCache PublishedContentHybridCache => GetRequiredService<IPublishedContentCache>();
private IContentEditingService ContentEditingService => GetRequiredService<IContentEditingService>();
private IContentPublishingService ContentPublishingService => GetRequiredService<IContentPublishingService>();
private IDocumentCacheService DocumentCacheService => GetRequiredService<IDocumentCacheService>();
private const string NewName = "New Name";
private const string NewTitle = "New Title";
@@ -467,6 +468,61 @@ internal sealed class DocumentHybridCacheTests : UmbracoIntegrationTestWithConte
Assert.IsNull(textPage);
}
[Test]
public async Task Can_Get_Published_Content_By_Id_After_Previous_Check_Where_Not_Found()
{
// Arrange
var testPageKey = Guid.NewGuid();
// Act & Assert
// - assert we cannot get the content that doesn't yet exist from the cache
var testPage = await PublishedContentHybridCache.GetByIdAsync(testPageKey);
Assert.IsNull(testPage);
testPage = await PublishedContentHybridCache.GetByIdAsync(testPageKey);
Assert.IsNull(testPage);
// - create and publish the content
var testPageContent = ContentEditingBuilder.CreateBasicContent(ContentType.Key, testPageKey);
var createResult = await ContentEditingService.CreateAsync(testPageContent, Constants.Security.SuperUserKey);
Assert.IsTrue(createResult.Success);
var publishResult = await ContentPublishingService.PublishAsync(testPageKey, CultureAndSchedule, Constants.Security.SuperUserKey);
Assert.IsTrue(publishResult.Success);
// - assert we can now get the content from the cache
testPage = await PublishedContentHybridCache.GetByIdAsync(testPageKey);
Assert.IsNotNull(testPage);
}
[Test]
public async Task Can_Get_Published_Content_By_Id_After_Previous_Exists_Check()
{
// Act
var hasContentForTextPageCached = await DocumentCacheService.HasContentByIdAsync(PublishedTextPageId);
Assert.IsTrue(hasContentForTextPageCached);
var textPage = await PublishedContentHybridCache.GetByIdAsync(PublishedTextPageId);
// Assert
AssertPublishedTextPage(textPage);
}
[Test]
public async Task Can_Do_Exists_Check_On_Created_Published_Content()
{
var testPageKey = Guid.NewGuid();
var testPageContent = ContentEditingBuilder.CreateBasicContent(ContentType.Key, testPageKey);
var createResult = await ContentEditingService.CreateAsync(testPageContent, Constants.Security.SuperUserKey);
Assert.IsTrue(createResult.Success);
var publishResult = await ContentPublishingService.PublishAsync(testPageKey, CultureAndSchedule, Constants.Security.SuperUserKey);
Assert.IsTrue(publishResult.Success);
var testPage = await PublishedContentHybridCache.GetByIdAsync(testPageKey);
Assert.IsNotNull(testPage);
var hasContentForTextPageCached = await DocumentCacheService.HasContentByIdAsync(testPage.Id);
Assert.IsTrue(hasContentForTextPageCached);
}
private void AssertTextPage(IPublishedContent textPage)
{
Assert.Multiple(() =>

View File

@@ -1,11 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Primitives;
using NUnit.Framework;
using Umbraco.Cms.Core.PropertyEditors;
@@ -31,6 +27,17 @@ public class ObjectExtensionsTests
private CultureInfo _savedCulture;
[Test]
public void Can_Create_Enumerable_Of_One()
{
var input = "hello";
#pragma warning disable CS0618 // Type or member is obsolete
var result = input.AsEnumerableOfOne<string>();
#pragma warning restore CS0618 // Type or member is obsolete
Assert.AreEqual(1, result.Count());
Assert.AreEqual("hello", result.First());
}
[Test]
public void Can_Convert_List_To_Enumerable()
{

View File

@@ -86,7 +86,7 @@ public class DateTimeUnspecifiedValueConverterTests
private static object[] _dateTimeUnspecifiedConvertToObjectCases =
[
new object[] { null, null },
new object[] { _convertToObjectInputDate, DateTime.Parse("2025-08-20T17:30:00") },
new object[] { _convertToObjectInputDate, DateTime.Parse("2025-08-20T16:30:00") },
];
[TestCaseSource(nameof(_dateTimeUnspecifiedConvertToObjectCases))]

View File

@@ -86,7 +86,7 @@ public class TimeOnlyValueConverterTests
private static object[] _timeOnlyConvertToObjectCases =
[
new object[] { null, null },
new object[] { _convertToObjectInputDate, TimeOnly.Parse("17:30") },
new object[] { _convertToObjectInputDate, TimeOnly.Parse("16:30") },
];
[TestCaseSource(nameof(_timeOnlyConvertToObjectCases))]

View File

@@ -1,3 +1,4 @@
using System;
using Microsoft.Extensions.Caching.Hybrid;
using Moq;
using NUnit.Framework;
@@ -33,15 +34,15 @@ public class HybridCacheExtensionsTests
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<ContentCacheNode>>>(),
It.IsAny<Func<CancellationToken, ValueTask<ContentCacheNode?>>>(),
It.IsAny<Func<Func<CancellationToken, ValueTask<ContentCacheNode?>>, CancellationToken, ValueTask<ContentCacheNode?>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(expectedValue);
// Act
var exists = await HybridCacheExtensions.ExistsAsync<ContentCacheNode>(_cacheMock.Object, key);
var exists = await HybridCacheExtensions.ExistsAsync<ContentCacheNode?>(_cacheMock.Object, key, CancellationToken.None);
// Assert
Assert.IsTrue(exists);
@@ -56,24 +57,24 @@ public class HybridCacheExtensionsTests
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<ContentCacheNode>>>(),
It.IsAny<Func<CancellationToken, ValueTask<ContentCacheNode?>>>(),
It.IsAny<Func<Func<CancellationToken, ValueTask<ContentCacheNode?>>, CancellationToken, ValueTask<ContentCacheNode?>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.Returns((
string key,
object? state,
Func<object, CancellationToken, ValueTask<ContentCacheNode>> factory,
Func<CancellationToken, ValueTask<ContentCacheNode?>> state,
Func<Func<CancellationToken, ValueTask<ContentCacheNode?>>, CancellationToken, ValueTask<ContentCacheNode?>> factory,
HybridCacheEntryOptions? options,
IEnumerable<string>? tags,
CancellationToken token) =>
{
return factory(state!, token);
return factory(state, token);
});
// Act
var exists = await HybridCacheExtensions.ExistsAsync<ContentCacheNode>(_cacheMock.Object, key);
var exists = await HybridCacheExtensions.ExistsAsync<ContentCacheNode?>(_cacheMock.Object, key, CancellationToken.None);
// Assert
Assert.IsFalse(exists);
@@ -89,15 +90,15 @@ public class HybridCacheExtensionsTests
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<string>>>(),
It.IsAny<Func<CancellationToken, ValueTask<string>>>(),
It.IsAny<Func<Func<CancellationToken, ValueTask<string>>, CancellationToken, ValueTask<string>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(expectedValue);
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<string>(_cacheMock.Object, key);
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<string>(_cacheMock.Object, key, CancellationToken.None);
// Assert
Assert.IsTrue(exists);
@@ -114,15 +115,15 @@ public class HybridCacheExtensionsTests
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<int>>>(),
It.IsAny<Func<CancellationToken, ValueTask<int>>>(),
It.IsAny<Func<Func<CancellationToken, ValueTask<int>>, CancellationToken, ValueTask<int>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(expectedValue);
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<int>(_cacheMock.Object, key);
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<int>(_cacheMock.Object, key, CancellationToken.None);
// Assert
Assert.IsTrue(exists);
@@ -138,15 +139,15 @@ public class HybridCacheExtensionsTests
_cacheMock
.Setup(cache => cache.GetOrCreateAsync(
key,
null!,
It.IsAny<Func<object, CancellationToken, ValueTask<object>>>(),
It.IsAny<Func<CancellationToken, ValueTask<object>>>(),
It.IsAny<Func<Func<CancellationToken, ValueTask<object>>, CancellationToken, ValueTask<object>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.ReturnsAsync(null!);
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<int?>(_cacheMock.Object, key);
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<int?>(_cacheMock.Object, key, CancellationToken.None);
// Assert
Assert.IsTrue(exists);
@@ -160,16 +161,16 @@ public class HybridCacheExtensionsTests
string key = "test-key";
_cacheMock.Setup(cache => cache.GetOrCreateAsync(
key,
null,
It.IsAny<Func<object?, CancellationToken, ValueTask<string>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
key,
It.IsAny<Func<CancellationToken, ValueTask<object>>>(),
It.IsAny<Func<Func<CancellationToken, ValueTask<object>>, CancellationToken, ValueTask<object>>>(),
It.IsAny<HybridCacheEntryOptions>(),
null,
CancellationToken.None))
.Returns((
string key,
object? state,
Func<object?, CancellationToken, ValueTask<string>> factory,
Func<CancellationToken, ValueTask<object>> state,
Func<Func<CancellationToken, ValueTask<object>>, CancellationToken, ValueTask<object>> factory,
HybridCacheEntryOptions? options,
IEnumerable<string>? tags,
CancellationToken token) =>
@@ -178,7 +179,7 @@ public class HybridCacheExtensionsTests
});
// Act
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<string>(_cacheMock.Object, key);
var (exists, value) = await HybridCacheExtensions.TryGetValueAsync<object>(_cacheMock.Object, key, CancellationToken.None);
// Assert
Assert.IsFalse(exists);