diff --git a/Directory.Packages.props b/Directory.Packages.props index 0f029e9754..71b1c04348 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,25 +12,25 @@ - - + + - - - - - + + + + + - + - - + + - + @@ -44,9 +44,9 @@ - - - + + + @@ -62,7 +62,7 @@ - + @@ -73,12 +73,11 @@ - + - diff --git a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs index 2cfa3239fd..980a5f4a6d 100644 --- a/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs +++ b/src/Umbraco.Cms.Persistence.EFCore/UmbracoDbContext.cs @@ -36,19 +36,14 @@ public class UmbracoDbContext : DbContext /// /// public UmbracoDbContext(DbContextOptions options) - : base(ConfigureOptions(options, out IOptionsMonitor? connectionStringsOptionsMonitor)) + : base(ConfigureOptions(options)) { - connectionStringsOptionsMonitor.OnChange(c => - { - ILogger logger = StaticServiceProvider.Instance.GetRequiredService>(); - logger.LogWarning("Connection string changed, disposing context"); - Dispose(); - }); + } - private static DbContextOptions ConfigureOptions(DbContextOptions options, out IOptionsMonitor connectionStringsOptionsMonitor) + private static DbContextOptions ConfigureOptions(DbContextOptions options) { - connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService>(); + IOptionsMonitor connectionStringsOptionsMonitor = StaticServiceProvider.Instance.GetRequiredService>(); ConnectionStrings connectionStrings = connectionStringsOptionsMonitor.CurrentValue; diff --git a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs index 8d792a5ef7..438c66a2c1 100644 --- a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs @@ -118,15 +118,35 @@ public static class DistributedCacheExtensions #region ContentCacheRefresher public static void RefreshAllContentCache(this DistributedCache dc) + { + ContentCacheRefresher.JsonPayload[] payloads = new[] + { + new ContentCacheRefresher.JsonPayload() + { + ChangeTypes = TreeChangeTypes.RefreshAll + } + }; + // note: refresh all content cache does refresh content types too - => dc.RefreshByPayload(ContentCacheRefresher.UniqueId, new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll).Yield()); + dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads); + } [Obsolete("Use the overload accepting IEnumerable instead to avoid allocating arrays. This overload will be removed in Umbraco 13.")] public static void RefreshContentCache(this DistributedCache dc, TreeChange[] changes) => dc.RefreshContentCache(changes.AsEnumerable()); public static void RefreshContentCache(this DistributedCache dc, IEnumerable> changes) - => dc.RefreshByPayload(ContentCacheRefresher.UniqueId, changes.DistinctBy(x => (x.Item.Id, x.Item.Key, x.ChangeTypes)).Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes))); + { + IEnumerable payloads = changes.Select(x => new ContentCacheRefresher.JsonPayload() + { + Id = x.Item.Id, + Key = x.Item.Key, + ChangeTypes = x.ChangeTypes, + Blueprint = x.Item.Blueprint + }); + + dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads); + } #endregion diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs index a515d5c5d1..779b22fe68 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs @@ -84,8 +84,8 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase((k, v) => v.Path?.Contains(pathid) ?? false); } - // if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content - if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) + // if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content + if (payload.Blueprint is false && payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) { idsRemoved.Add(payload.Id); } @@ -120,7 +120,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase x.Blueprint is false)) + { + // Only notify if the payload contains actual (non-blueprint) contents + NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads); + } base.Refresh(payloads); } @@ -157,8 +161,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase(); - } - + deleteBatch ??= new HashSet(); deleteBatch.Add(payload.Id); } else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index 175cf766c5..8aa012d11f 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -220,7 +220,16 @@ internal class PublishedSnapshotService : IPublishedSnapshotService // they require. using (_contentStore.GetScopedWriteLock(_scopeProvider)) { - NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + NotifyLocked( + new[] + { + new ContentCacheRefresher.JsonPayload() + { + ChangeTypes = TreeChangeTypes.RefreshAll + } + }, + out _, + out _); } using (_mediaStore.GetScopedWriteLock(_scopeProvider)) @@ -885,6 +894,12 @@ internal class PublishedSnapshotService : IPublishedSnapshotService _logger.LogDebug("Notified {ChangeTypes} for content {ContentId}", payload.ChangeTypes, payload.Id); } + if (payload.Blueprint) + { + // Skip blueprints + continue; + } + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) { using (IScope scope = _scopeProvider.CreateScope()) diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs index f7be9d129a..c75bbd5a80 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Web.BackOffice.Filters; /// Validates the incoming model along with if the user is allowed to perform the /// operation /// -internal sealed class ContentSaveValidationAttribute : TypeFilterAttribute +public sealed class ContentSaveValidationAttribute : TypeFilterAttribute { public ContentSaveValidationAttribute(bool skipUserAccessValidation = false) : base(typeof(ContentSaveValidationFilter)) diff --git a/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs b/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs index bc07497fcd..bf8c7372bc 100644 --- a/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs +++ b/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Web.BackOffice.ModelBinders; -internal class BlueprintItemBinder : ContentItemBinder +public class BlueprintItemBinder : ContentItemBinder { private readonly IContentService _contentService; diff --git a/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs b/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs index 0842ca2051..c73a45f904 100644 --- a/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs +++ b/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Web.BackOffice.ModelBinders; /// /// The model binder for /// -internal class ContentItemBinder : IModelBinder +public class ContentItemBinder : IModelBinder { private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js index 1367d58151..ab6c176c85 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/blockeditormodelobject.service.js @@ -367,7 +367,6 @@ * @name load * @methodOf umbraco.services.blockEditorModelObject * @description Load the scaffolding models for the given configuration, these are needed to provide useful models for each block. - * @param {Object} blockObject BlockObject to receive data values from. * @returns {Promise} A Promise object which resolves when all scaffold models are loaded. */ load: function () { @@ -398,9 +397,15 @@ scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index); if(scaffoldKeys.length > 0) { - var currentPage = editorState.getCurrent(); - var currentPageId = currentPage ? (currentPage.id > 0 ? currentPage.id : currentPage.parentId) : null || -20; + // We need to know if we are in the document type editor or content editor. + // If we are in the document type editor, we need to use -20 as the current page id. + // If we are in the content editor, we need to use the current page id or parent id if the current page is new. + // We can recognize a content editor context by checking if the current editor state has a contentTypeKey. + const currentEditorState = editorState.getCurrent(); + const currentPageId = currentEditorState.contentTypeKey ? currentEditorState.id || currentEditorState.parentId || -20 : -20; + // Load all scaffolds for the block types. + // The currentPageId is used to determine the access level for the current user. tasks.push(contentResource.getScaffoldByKeys(currentPageId, scaffoldKeys).then(scaffolds => { Object.values(scaffolds).forEach(scaffold => { // self.scaffolds might not exists anymore, this happens if this instance has been destroyed before the load is complete. diff --git a/src/Umbraco.Web.UI.Docs/.nvmrc b/src/Umbraco.Web.UI.Docs/.nvmrc new file mode 100644 index 0000000000..ec3053c8dc --- /dev/null +++ b/src/Umbraco.Web.UI.Docs/.nvmrc @@ -0,0 +1 @@ +10.15 diff --git a/tests/Directory.Packages.props b/tests/Directory.Packages.props index 1ecbee80a5..586469c2c8 100644 --- a/tests/Directory.Packages.props +++ b/tests/Directory.Packages.props @@ -4,8 +4,8 @@ - - + + diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs index 92ef2b5c9f..e509742cb9 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs @@ -18,8 +18,10 @@ public class RefresherTests { new MediaCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None), }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -30,13 +32,21 @@ public class RefresherTests { ContentCacheRefresher.JsonPayload[] source = { - new ContentCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None), + new ContentCacheRefresher.JsonPayload() + { + Id = 1234, + Key = Guid.NewGuid(), + ChangeTypes = TreeChangeTypes.None + } }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); + Assert.AreEqual(source[0].Blueprint, payload[0].Blueprint); } [Test] @@ -46,8 +56,10 @@ public class RefresherTests { new ContentTypeCacheRefresher.JsonPayload("xxx", 1234, ContentTypeChangeTypes.None), }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].ItemType, payload[0].ItemType); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -60,8 +72,10 @@ public class RefresherTests { new DataTypeCacheRefresher.JsonPayload(1234, Guid.NewGuid(), true), }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].Removed, payload[0].Removed); @@ -70,10 +84,14 @@ public class RefresherTests [Test] public void DomainCacheRefresherCanDeserializeJsonPayload() { - DomainCacheRefresher.JsonPayload[] - source = { new DomainCacheRefresher.JsonPayload(1234, DomainChangeTypes.None) }; + DomainCacheRefresher.JsonPayload[] source = + { + new DomainCacheRefresher.JsonPayload(1234, DomainChangeTypes.None) + }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].ChangeType, payload[0].ChangeType); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs index a007079ca9..b516b4fffb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs @@ -358,7 +358,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(10, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 10, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _); // changes that *I* make are immediately visible on the current snapshot var documents = snapshot.Content.GetAtRoot().ToArray(); @@ -392,7 +401,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _); // changes that *I* make are immediately visible on the current snapshot var documents = snapshot.Content.GetAtRoot().ToArray(); @@ -450,7 +468,11 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] { - new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, Guid.Empty, TreeChangeTypes.RefreshBranch), + new ContentCacheRefresher.JsonPayload() + { + Id = kit.Node.ParentContentId, + ChangeTypes = TreeChangeTypes.RefreshBranch + } }, out _, out _); @@ -516,11 +538,19 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( new[] - { - // removal must come first - new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshBranch), - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch), - }, + { + // removal must come first + new ContentCacheRefresher.JsonPayload() + { + Id = 2, + ChangeTypes = TreeChangeTypes.RefreshBranch + }, + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, out _, out _); @@ -571,7 +601,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify - which ensures there are 2 generations in the cache meaning each LinkedNode has a Next value. SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 4, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _); // refresh the branch again, this used to show the issue where a null ref exception would occur // because in the ClearBranchLocked logic, when SetValueLocked was called within a recursive call @@ -579,7 +618,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // this value before recursing. Assert.DoesNotThrow(() => SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _)); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 4, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _)); } [Test] @@ -759,11 +807,23 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove), // remove last - new ContentCacheRefresher.JsonPayload(5, Guid.Empty, TreeChangeTypes.Remove), // remove middle - new ContentCacheRefresher.JsonPayload(9, Guid.Empty, TreeChangeTypes.Remove), // remove first - }, + { + new ContentCacheRefresher.JsonPayload() // remove last + { + Id = 3, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove middle + { + Id = 5, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove first + { + Id = 9, + ChangeTypes = TreeChangeTypes.Remove + } + }, out _, out _); @@ -779,11 +839,23 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.Remove), // remove first - new ContentCacheRefresher.JsonPayload(8, Guid.Empty, TreeChangeTypes.Remove), // remove - new ContentCacheRefresher.JsonPayload(7, Guid.Empty, TreeChangeTypes.Remove), // remove - }, + { + new ContentCacheRefresher.JsonPayload() // remove first + { + Id = 1, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove + { + Id = 8, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove + { + Id = 7, + ChangeTypes = TreeChangeTypes.Remove + } + }, out _, out _); @@ -823,8 +895,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] { - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch), - new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshNode), + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshBranch + }, + new ContentCacheRefresher.JsonPayload() + { + Id = 2, + ChangeTypes = TreeChangeTypes.RefreshNode + } }, out _, out _); @@ -887,7 +967,17 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT var parentNode = parentNodes[0]; AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 2); - SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.Remove) }, out _, out _); + SnapshotService.Notify( + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 2, + ChangeTypes = TreeChangeTypes.Remove + } + }, + out _, + out _); parentNodes = contentStore.Test.GetValues(1); parentNode = parentNodes[0]; @@ -944,9 +1034,13 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove), // remove middle child - }, + { + new ContentCacheRefresher.JsonPayload() // remove middle child + { + Id = 3, + ChangeTypes = TreeChangeTypes.Remove + } + }, out _, out _); @@ -1013,7 +1107,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT Assert.IsFalse(contentStore.Test.NextGen); SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshNode + } + }, + out _, + out _); Assert.AreEqual(2, contentStore.Test.LiveGen); Assert.IsTrue(contentStore.Test.NextGen); @@ -1084,7 +1187,17 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT published ? rootKit.PublishedData : null); NuCacheContentService.ContentKits[1] = kit; - SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, changeType) }, out _, out _); + SnapshotService.Notify( + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = changeType + } + }, + out _, + out _); Assert.AreEqual(assertGen, contentStore.Test.LiveGen); Assert.IsTrue(contentStore.Test.NextGen); @@ -1162,9 +1275,13 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.RefreshBranch), // remove middle child - }, + { + new ContentCacheRefresher.JsonPayload() // remove middle child + { + Id = 3, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, out _, out _);