'
await expect(await umbracoApi.content.verifyRenderedContent('/contentpickercontent', expectedContent, true)).toBeTruthy();
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/routing.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/routing.spec.ts
index 43f1d52308..7a67a4e981 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/routing.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Content/routing.spec.ts
@@ -105,7 +105,7 @@ test.describe('Routing', () => {
await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.saveAndPublish));
// Pop-up with what cultures you want to publish shows, click it
- await page.locator('.btn-success').last().click()
+ await umbracoUi.clickDataElementByElementName('button-overlaySubmit');
// Assert
await umbracoUi.isSuccessNotificationVisible();
@@ -161,7 +161,7 @@ test.describe('Routing', () => {
await expect(await page.locator('.umb-list')).toBeVisible();
await page.locator('.checkbox').last().click();
// Pop-up with what cultures you want to publish shows, click it
- await page.locator('.btn-success').last().click()
+ await umbracoUi.clickDataElementByElementName('button-overlaySubmit');
// Assert
await expect(await umbracoUi.getSuccessNotification()).toHaveCount(2, {timeout: 20000});
@@ -239,7 +239,7 @@ test.describe('Routing', () => {
await expect(await page.locator('.umb-list')).toBeVisible();
await page.locator('.checkbox').last().click();
- await page.locator('.btn-success').last().click()
+ await umbracoUi.clickDataElementByElementName('button-overlaySubmit');
await umbracoUi.clickMultiple(page.locator('.alert-success > .close'));
await umbracoUi.clickElement(umbracoUi.getTreeItem("content", [nodeName, childNodeName, grandChildNodeName]));
@@ -247,7 +247,7 @@ test.describe('Routing', () => {
await expect(await page.locator('.umb-list')).toBeVisible();
await page.locator('.checkbox').last().click();
- await page.locator('.btn-success').last().click()
+ await umbracoUi.clickDataElementByElementName('button-overlaySubmit');
// Assert
await expect(await umbracoUi.getSuccessNotification()).toHaveCount(2, {timeout: 20000});
})
diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/partialViews.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/partialViews.spec.ts
index 9cdedbeaef..2763f1134d 100644
--- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/partialViews.spec.ts
+++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/partialViews.spec.ts
@@ -37,7 +37,7 @@ test.describe('Partial Views', () => {
await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.save));
//Assert
- await umbracoUi.isSuccessNotificationVisible({timeout: 30000});
+ await umbracoUi.isSuccessNotificationVisible();
//Clean up
await umbracoApi.partialViews.ensureNameNotExists('', fileName);
@@ -61,7 +61,7 @@ test.describe('Partial Views', () => {
await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.save));
// Assert
- await umbracoUi.isSuccessNotificationVisible({timeout:20000});
+ await umbracoUi.isSuccessNotificationVisible();
// Clean up
await umbracoApi.partialViews.ensureNameNotExists('', fileName);
@@ -131,7 +131,7 @@ test.describe('Partial Views', () => {
await umbracoUi.clickElement(umbracoUi.getButtonByLabelKey(ConstantHelper.buttons.save));
// Assert
- await umbracoUi.isSuccessNotificationVisible({timeout:20000});
+ await umbracoUi.isSuccessNotificationVisible();
// Clean
await umbracoApi.partialViews.ensureNameNotExists('', fileName);
});
diff --git a/tests/Umbraco.Tests.Common/TestHelperBase.cs b/tests/Umbraco.Tests.Common/TestHelperBase.cs
index ba7d29cd69..21c3c65b7d 100644
--- a/tests/Umbraco.Tests.Common/TestHelperBase.cs
+++ b/tests/Umbraco.Tests.Common/TestHelperBase.cs
@@ -7,6 +7,7 @@ using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
using Moq;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Cache;
@@ -14,6 +15,8 @@ using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Diagnostics;
+using Umbraco.Cms.Core.DistributedLocking;
+using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Hosting;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Logging;
@@ -24,6 +27,8 @@ using Umbraco.Cms.Core.Runtime;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Persistence;
+using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax;
+using Umbraco.Cms.Infrastructure.Scoping;
using Umbraco.Cms.Infrastructure.Serialization;
using Umbraco.Cms.Tests.Common.TestHelpers;
using Umbraco.Extensions;
@@ -76,6 +81,61 @@ public abstract class TestHelperBase
public IShortStringHelper ShortStringHelper { get; } =
new DefaultShortStringHelper(new DefaultShortStringHelperConfig());
+ public IScopeProvider ScopeProvider
+ {
+ get
+ {
+ var loggerFactory = NullLoggerFactory.Instance;
+ var fileSystems = new FileSystems(
+ loggerFactory,
+ Mock.Of(),
+ Mock.Of>(),
+ Mock.Of());
+ var mediaFileManager = new MediaFileManager(
+ Mock.Of(),
+ Mock.Of(),
+ loggerFactory.CreateLogger(),
+ Mock.Of(),
+ Mock.Of(),
+ Options.Create(new ContentSettings()));
+ var databaseFactory = new Mock();
+ var database = new Mock();
+ var sqlContext = new Mock();
+
+ var lockingMechanism = new Mock();
+ lockingMechanism.Setup(x => x.ReadLock(It.IsAny(), It.IsAny()))
+ .Returns(Mock.Of());
+ lockingMechanism.Setup(x => x.WriteLock(It.IsAny(), It.IsAny()))
+ .Returns(Mock.Of());
+
+ var lockingMechanismFactory = new Mock();
+ lockingMechanismFactory.Setup(x => x.DistributedLockingMechanism)
+ .Returns(lockingMechanism.Object);
+
+ // Setup mock of database factory to return mock of database.
+ databaseFactory.Setup(x => x.CreateDatabase()).Returns(database.Object);
+ databaseFactory.Setup(x => x.SqlContext).Returns(sqlContext.Object);
+
+ // Setup mock of database to return mock of sql SqlContext
+ database.Setup(x => x.SqlContext).Returns(sqlContext.Object);
+
+ var syntaxProviderMock = new Mock();
+
+ // Setup mock of ISqlContext to return syntaxProviderMock
+ sqlContext.Setup(x => x.SqlSyntax).Returns(syntaxProviderMock.Object);
+
+ return new ScopeProvider(
+ new AmbientScopeStack(),
+ new AmbientScopeContextStack(),
+ lockingMechanismFactory.Object,
+ databaseFactory.Object,
+ fileSystems,
+ new TestOptionsMonitor(new CoreDebugSettings()),
+ mediaFileManager,
+ loggerFactory,
+ Mock.Of());
+ }
+ }
public IJsonSerializer JsonSerializer { get; } = new JsonNetSerializer();
diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs
index 02efa11a07..48cc197be6 100644
--- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs
+++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServicePublishBranchTests.cs
@@ -92,7 +92,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest
AssertPublishResults(
r,
x => x.Result,
- PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it.
+ PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublishAlready);
@@ -139,7 +139,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest
AssertPublishResults(
r,
x => x.Result,
- PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it.
+ PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublish,
@@ -184,7 +184,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest
var r = ContentService.SaveAndPublishBranch(vRoot, false)
.ToArray(); // no culture specified so "*" is used, so all cultures
- Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it.
+ Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result);
Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result);
}
@@ -220,7 +220,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest
var saveResult = ContentService.Save(iv1);
var r = ContentService.SaveAndPublishBranch(vRoot, false, "de").ToArray();
- Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[0].Result); // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it.
+ Assert.AreEqual(PublishResultType.SuccessPublishAlready, r[0].Result);
Assert.AreEqual(PublishResultType.SuccessPublishCulture, r[1].Result);
}
@@ -380,7 +380,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest
AssertPublishResults(
r,
x => x.Result,
- PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it.
+ PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublish,
PublishResultType.SuccessPublishCulture);
@@ -406,7 +406,7 @@ public class ContentServicePublishBranchTests : UmbracoIntegrationTest
AssertPublishResults(
r,
x => x.Result,
- PublishResultType.SuccessPublish, // During branch publishing, the change detection of the root branch runs AFTER the check to process the branchItem => root is always saved&Published as the intent requires it.
+ PublishResultType.SuccessPublishAlready,
PublishResultType.SuccessPublish,
PublishResultType.SuccessPublishCulture);
diff --git a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
index 33b89f1738..24b15b5723 100644
--- a/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
+++ b/tests/Umbraco.Tests.UnitTests/TestHelpers/TestHelper.cs
@@ -31,6 +31,7 @@ using Umbraco.Cms.Core.Net;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Routing;
using Umbraco.Cms.Core.Runtime;
+using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Mail;
@@ -42,6 +43,7 @@ using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Extensions;
using File = System.IO.File;
using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment;
+using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider;
namespace Umbraco.Cms.Tests.UnitTests.TestHelpers;
@@ -58,6 +60,8 @@ public static class TestHelper
/// The assembly directory.
public static string WorkingDirectory => s_testHelperInternal.WorkingDirectory;
+ public static IScopeProvider ScopeProvider => s_testHelperInternal.ScopeProvider;
+ public static ICoreScopeProvider CoreScopeProvider => s_testHelperInternal.ScopeProvider;
public static IShortStringHelper ShortStringHelper => s_testHelperInternal.ShortStringHelper;
public static IJsonSerializer JsonSerializer => s_testHelperInternal.JsonSerializer;
diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheVarianceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheVarianceTests.cs
new file mode 100644
index 0000000000..72a68b443c
--- /dev/null
+++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Published/PropertyCacheVarianceTests.cs
@@ -0,0 +1,381 @@
+using Moq;
+using NUnit.Framework;
+using Umbraco.Cms.Core.Cache;
+using Umbraco.Cms.Core.Models;
+using Umbraco.Cms.Core.Models.PublishedContent;
+using Umbraco.Cms.Core.PropertyEditors;
+using Umbraco.Cms.Core.PublishedCache;
+using Umbraco.Cms.Infrastructure.PublishedCache;
+using Umbraco.Cms.Infrastructure.PublishedCache.DataSource;
+using Property = Umbraco.Cms.Infrastructure.PublishedCache.Property;
+
+namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Published;
+
+[TestFixture]
+public class PropertyCacheVarianceTests
+{
+ // This class tests various permutations of property value calculation across variance types and cache levels.
+ //
+ // Properties contain different "value levels", all of which are cached:
+ // 1. The source value => the "raw" value from the client side editor (it can be different, but it's easiest to think of it like that).
+ // 2. The intermediate value => a "temporary" value that is used to calculate the various "final" values.
+ // 3. The object value => the "final" object value that is exposed in an IPublishedElement output.
+ // 4. The XPath value => a legacy "final" value, don't think too hard on it.
+ // 3. The delivery API object value => the "final" object value that is exposed in the Delivery API.
+ //
+ // Property values are cached based on a few rules:
+ // 1. The property type variation and the parent content type variation determines how the intermediate value is cached.
+ // The effective property variation is a product of both variations, meaning the property type and the content type
+ // variations are combined in an OR.
+ // The rules are as follows:
+ // - ContentVariation.Nothing => the intermediate value is calculated once and reused across all variants (cultures and segments).
+ // - ContentVariation.Culture => the intermediate value is calculated per culture and reused across all segments.
+ // - ContentVariation.Segment => the intermediate value is calculated per segment and reused across all cultures.
+ // - ContentVariation.CultureAndSegment => the intermediate value is calculated for all invoked culture and segment combinations.
+ // 2. The property type cache level (which is usually derived from the property value converter).
+ // - PropertyCacheLevel.Element => the final values are cached until the parent content item is updated.
+ // - PropertyCacheLevel.Elements => the final values are cached until the _any_ content item is updated.
+ // - PropertyCacheLevel.Snapshot => the final values are cached for the duration of the active cache snapshot (i.e. until the end of the current request).
+ // - PropertyCacheLevel.None => the final values are never cached and will be re-calculated each time they're requested.
+
+ // ### Invariant content type + invariant property type ###
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Element,
+ // no variation => the intermediate value is calculated only once
+ // cache level => the final value is calculated only once
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Elements,
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Snapshot,
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.None,
+ // no variation => the intermediate value is calculated once
+ // no cache => the final value is calculated for each request (reflects both changes in culture and segments)
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (da-DK:segment1)",
+ "en-US:segment2 (da-DK:segment1)",
+ "da-DK:segment2 (da-DK:segment1)")]
+ // ### Culture variant content type + invariant property type ###
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Element,
+ // culture variation => the intermediate value is calculated per culture (ignores segment changes until a culture changes)
+ // cache level => the final value is calculated only once per culture (ignores segment changes until a culture changes)
+ // NOTE: in this test, culture changes before segment, so the updated segment is never reflected here
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Elements,
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Snapshot,
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.None,
+ // culture variation => the intermediate value is calculated per culture (ignores segment changes until a culture changes)
+ // no cache => the final value is calculated for each request (reflects both changes in culture and segments)
+ // NOTE: in this test, culture changes before segment, so the updated segment is never reflected in the intermediate value here
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment1)",
+ "da-DK:segment2 (da-DK:segment1)")]
+ // NOTE: As the tests above show, cache levels Element, Elements and Snapshot all yield the same values in this
+ // test, because we are efficiently executing the test in a snapshot. From here on out we're only building
+ // test cases for Element and None.
+ // ### Segment variant content type + invariant property type ###
+ [TestCase(
+ ContentVariation.Segment,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Element,
+ // segment variation => the intermediate value is calculated per segment (ignores culture changes until a segment changes)
+ // cache level => the final value is calculated only once per segment (ignores culture changes until a segment changes)
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "en-US:segment2 (en-US:segment2)")]
+ [TestCase(
+ ContentVariation.Segment,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.None,
+ // segment variation => the intermediate value is calculated per segment (ignores culture changes until a segment changes)
+ // no cache => the final value is calculated for each request (reflects both changes in culture and segments)
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (da-DK:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (en-US:segment2)")]
+ // ### Culture and segment variant content type + invariant property type ###
+ [TestCase(
+ ContentVariation.CultureAndSegment,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.Element,
+ // culture and segment variation => the intermediate value is calculated per culture and segment
+ // cache level => the final value is calculated only once per culture and segment (efficiently on every request in this test)
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ [TestCase(
+ ContentVariation.CultureAndSegment,
+ ContentVariation.Nothing,
+ PropertyCacheLevel.None,
+ // culture and segment variation => the intermediate value is calculated per culture and segment
+ // no cache => the final value is calculated for each request
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ // ### Invariant content type + culture variant property type ###
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Culture,
+ PropertyCacheLevel.Element,
+ // same behaviour as culture variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Culture,
+ PropertyCacheLevel.None,
+ // same behaviour as culture variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment1)",
+ "da-DK:segment2 (da-DK:segment1)")]
+ // ### Invariant content type + segment variant property type ###
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Segment,
+ PropertyCacheLevel.Element,
+ // same behaviour as segment variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "en-US:segment2 (en-US:segment2)")]
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.Segment,
+ PropertyCacheLevel.None,
+ // same behaviour as segment variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (da-DK:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (en-US:segment2)")]
+ // ### Invariant content type + culture and segment variant property type ###
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.CultureAndSegment,
+ PropertyCacheLevel.Element,
+ // same behaviour as culture and segment variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ [TestCase(
+ ContentVariation.Nothing,
+ ContentVariation.CultureAndSegment,
+ PropertyCacheLevel.None,
+ // same behaviour as culture and segment variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ // ### Culture variant content type + segment variant property type ###
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Segment,
+ PropertyCacheLevel.Element,
+ // same behaviour as culture and segment variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Segment,
+ PropertyCacheLevel.None,
+ // same behaviour as culture and segment variation on content type + no variation on property type, see comments above
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ public void ContentType_PropertyType_Variation_Cache_Values(
+ ContentVariation contentTypeVariation,
+ ContentVariation propertyTypeVariation,
+ PropertyCacheLevel propertyCacheLevel,
+ string expectedValue1DaDkSegment1,
+ string expectedValue2EnUsSegment1,
+ string expectedValue3EnUsSegment2,
+ string expectedValue4DaDkSegment2)
+ {
+ var variationContextCulture = "da-DK";
+ var variationContextSegment = "segment1";
+ var property = CreateProperty(
+ contentTypeVariation,
+ propertyTypeVariation,
+ propertyCacheLevel,
+ () => variationContextCulture,
+ () => variationContextSegment);
+
+ Assert.AreEqual(expectedValue1DaDkSegment1, property.GetValue());
+
+ variationContextCulture = "en-US";
+ Assert.AreEqual(expectedValue2EnUsSegment1, property.GetValue());
+
+ variationContextSegment = "segment2";
+ Assert.AreEqual(expectedValue3EnUsSegment2, property.GetValue());
+
+ variationContextCulture = "da-DK";
+ Assert.AreEqual(expectedValue4DaDkSegment2, property.GetValue());
+ }
+
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Nothing,
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "da-DK:segment1 (da-DK:segment1)")]
+ [TestCase(
+ ContentVariation.Segment,
+ ContentVariation.Nothing,
+ "da-DK:segment1 (da-DK:segment1)",
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "en-US:segment2 (en-US:segment2)")]
+ [TestCase(
+ ContentVariation.Culture,
+ ContentVariation.Segment,
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ [TestCase(
+ ContentVariation.CultureAndSegment,
+ ContentVariation.Nothing,
+ "da-DK:segment1 (da-DK:segment1)",
+ "en-US:segment1 (en-US:segment1)",
+ "en-US:segment2 (en-US:segment2)",
+ "da-DK:segment2 (da-DK:segment2)")]
+ public void ContentType_PropertyType_Variation_Are_Interchangeable(
+ ContentVariation variation1,
+ ContentVariation variation2,
+ string expectedValue1DaDkSegment1,
+ string expectedValue2EnUsSegment1,
+ string expectedValue3EnUsSegment2,
+ string expectedValue4DaDkSegment2)
+ {
+ var scenarios = new[]
+ {
+ new { ContentTypeVariation = variation1, PropertyTypeVariation = variation2 },
+ new { ContentTypeVariation = variation2, PropertyTypeVariation = variation1 }
+ };
+
+ foreach (var scenario in scenarios)
+ {
+ var variationContextCulture = "da-DK";
+ var variationContextSegment = "segment1";
+ var property = CreateProperty(
+ scenario.ContentTypeVariation,
+ scenario.PropertyTypeVariation,
+ PropertyCacheLevel.Element,
+ () => variationContextCulture,
+ () => variationContextSegment);
+
+ Assert.AreEqual(expectedValue1DaDkSegment1, property.GetValue());
+
+ variationContextCulture = "en-US";
+ Assert.AreEqual(expectedValue2EnUsSegment1, property.GetValue());
+
+ variationContextSegment = "segment2";
+ Assert.AreEqual(expectedValue3EnUsSegment2, property.GetValue());
+
+ variationContextCulture = "da-DK";
+ Assert.AreEqual(expectedValue4DaDkSegment2, property.GetValue());
+ }
+ }
+
+ ///
+ /// Creates a new property with a mocked publishedSnapshotAccessor that uses a VariationContext that reads culture and segment information from the passed in functions.
+ ///
+ private Property CreateProperty(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation, PropertyCacheLevel propertyTypeCacheLevel, Func getCulture, Func getSegment)
+ {
+ var contentType = new Mock();
+ contentType.SetupGet(c => c.PropertyTypes).Returns(Array.Empty());
+ contentType.SetupGet(c => c.Variations).Returns(contentTypeVariation);
+
+ var contentNode = new ContentNode(123, Guid.NewGuid(), contentType.Object, 1, string.Empty, 1, 1, DateTime.Now, 1);
+ var contentData = new ContentData("bla", "bla", 1, DateTime.Now, 1, 1, true, new Dictionary(), null);
+
+ var elementCache = new FastDictionaryAppCache();
+ var snapshotCache = new FastDictionaryAppCache();
+ var publishedSnapshotMock = new Mock();
+ publishedSnapshotMock.SetupGet(p => p.ElementsCache).Returns(elementCache);
+ publishedSnapshotMock.SetupGet(p => p.SnapshotCache).Returns(snapshotCache);
+
+ var publishedSnapshot = publishedSnapshotMock.Object;
+ var publishedSnapshotAccessor = new Mock();
+ publishedSnapshotAccessor.Setup(p => p.TryGetPublishedSnapshot(out publishedSnapshot)).Returns(true);
+
+ var variationContextAccessorMock = new Mock();
+ variationContextAccessorMock
+ .SetupGet(mock => mock.VariationContext)
+ .Returns(() => new VariationContext(getCulture(), getSegment()));
+
+ var content = new PublishedContent(
+ contentNode,
+ contentData,
+ publishedSnapshotAccessor.Object,
+ variationContextAccessorMock.Object,
+ Mock.Of());
+
+ var propertyType = new Mock();
+ propertyType.SetupGet(p => p.CacheLevel).Returns(propertyTypeCacheLevel);
+ propertyType.SetupGet(p => p.DeliveryApiCacheLevel).Returns(propertyTypeCacheLevel);
+ propertyType.SetupGet(p => p.Variations).Returns(propertyTypeVariation);
+ propertyType
+ .Setup(p => p.ConvertSourceToInter(It.IsAny(), It.IsAny