diff --git a/src/Umbraco.Core/Factories/NavigationFactory.cs b/src/Umbraco.Core/Factories/NavigationFactory.cs index 815312e048..a95cbf68a5 100644 --- a/src/Umbraco.Core/Factories/NavigationFactory.cs +++ b/src/Umbraco.Core/Factories/NavigationFactory.cs @@ -11,10 +11,10 @@ internal static class NavigationFactory /// /// A dictionary of objects with key corresponding to their unique Guid. /// The objects used to build the navigation nodes dictionary. - public static void BuildNavigationDictionary(ConcurrentDictionary nodesStructure,IEnumerable entities) + public static void BuildNavigationDictionary(ConcurrentDictionary nodesStructure, IEnumerable entities) { var entityList = entities.ToList(); - var idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key); + Dictionary idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key); foreach (INavigationModel entity in entityList) { diff --git a/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs b/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs index 394223c311..4f2d1e1c9b 100644 --- a/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs +++ b/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs @@ -33,12 +33,12 @@ internal abstract class ContentNavigationServiceBase public bool TryGetParentKey(Guid childKey, out Guid? parentKey) => TryGetParentKeyFromStructure(_navigationStructure, childKey, out parentKey); + public bool TryGetRootKeys(out IEnumerable rootKeys) + => TryGetRootKeysFromStructure(_navigationStructure, out rootKeys); + public bool TryGetChildrenKeys(Guid parentKey, out IEnumerable childrenKeys) => TryGetChildrenKeysFromStructure(_navigationStructure, parentKey, out childrenKeys); - public bool TryGetRootKeys(out IEnumerable childrenKeys) - => TryGetRootKeysFromStructure(_navigationStructure, out childrenKeys); - public bool TryGetDescendantsKeys(Guid parentKey, out IEnumerable descendantsKeys) => TryGetDescendantsKeysFromStructure(_navigationStructure, parentKey, out descendantsKeys); @@ -165,7 +165,6 @@ internal abstract class ContentNavigationServiceBase _recycleBinNavigationStructure.TryRemove(key, out _); } - /// /// Rebuilds the navigation structure based on the specified object type key and whether the items are trashed. /// Only relevant for items in the content and media trees (which have readLock values of -333 or -334). @@ -184,11 +183,17 @@ internal abstract class ContentNavigationServiceBase using ICoreScope scope = _coreScopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(readLock); - IEnumerable navigationModels = trashed ? - _navigationRepository.GetTrashedContentNodesByObjectType(objectTypeKey) : - _navigationRepository.GetContentNodesByObjectType(objectTypeKey); - - NavigationFactory.BuildNavigationDictionary(_navigationStructure, navigationModels); + // Build the corresponding navigation structure + if (trashed) + { + IEnumerable navigationModels = _navigationRepository.GetTrashedContentNodesByObjectType(objectTypeKey); + NavigationFactory.BuildNavigationDictionary(_recycleBinNavigationStructure, navigationModels); + } + else + { + IEnumerable navigationModels = _navigationRepository.GetContentNodesByObjectType(objectTypeKey); + NavigationFactory.BuildNavigationDictionary(_navigationStructure, navigationModels); + } } private bool TryGetParentKeyFromStructure(ConcurrentDictionary structure, Guid childKey, out Guid? parentKey) @@ -204,6 +209,13 @@ internal abstract class ContentNavigationServiceBase return false; } + private bool TryGetRootKeysFromStructure(ConcurrentDictionary structure, out IEnumerable rootKeys) + { + // TODO can we make this more efficient? + rootKeys = structure.Values.Where(x => x.Parent is null).Select(x => x.Key); + return true; + } + private bool TryGetChildrenKeysFromStructure(ConcurrentDictionary structure, Guid parentKey, out IEnumerable childrenKeys) { if (structure.TryGetValue(parentKey, out NavigationNode? parentNode) is false) @@ -217,13 +229,6 @@ internal abstract class ContentNavigationServiceBase return true; } - private bool TryGetRootKeysFromStructure(ConcurrentDictionary structure, out IEnumerable childrenKeys) - { - // TODO can we make this more efficient? - childrenKeys = structure.Values.Where(x=>x.Parent is null).Select(x=>x.Key); - return true; - } - private bool TryGetDescendantsKeysFromStructure(ConcurrentDictionary structure, Guid parentKey, out IEnumerable descendantsKeys) { var descendants = new List(); diff --git a/src/Umbraco.Core/Services/Navigation/INavigationQueryService.cs b/src/Umbraco.Core/Services/Navigation/INavigationQueryService.cs index 9b6fb9807d..204ec657eb 100644 --- a/src/Umbraco.Core/Services/Navigation/INavigationQueryService.cs +++ b/src/Umbraco.Core/Services/Navigation/INavigationQueryService.cs @@ -8,8 +8,9 @@ public interface INavigationQueryService { bool TryGetParentKey(Guid childKey, out Guid? parentKey); + bool TryGetRootKeys(out IEnumerable rootKeys); + bool TryGetChildrenKeys(Guid parentKey, out IEnumerable childrenKeys); - bool TryGetRootKeys(out IEnumerable childrenKeys); bool TryGetDescendantsKeys(Guid parentKey, out IEnumerable descendantsKeys); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Copy.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Copy.cs index b3ebfa2215..95659b38be 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Copy.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Copy.cs @@ -3,17 +3,12 @@ using Umbraco.Cms.Core; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; -// TODO: test that it is added to its new parent - check parent's children -// TODO: test that it has the same amount of descendants - depending on value of includeDescendants param -// TODO: test that the number of target parent descendants updates when copying node with descendants -// TODO: test that copied node descendants have different keys than source node descendants public partial class DocumentNavigationServiceTests { [Test] [TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB", "A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2 to itself - [TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96", null)] // Child 2 to content root [TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", "C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 3 to Child 1 - public async Task Structure_Updates_When_Copying_Content(Guid nodeToCopy, Guid? targetParentKey) + public async Task Structure_Updates_When_Copying_Content(Guid nodeToCopy, Guid targetParentKey) { // Arrange DocumentNavigationQueryService.TryGetParentKey(nodeToCopy, out Guid? sourceParentKey); @@ -29,18 +24,86 @@ public partial class DocumentNavigationServiceTests Assert.Multiple(() => { - if (targetParentKey is null) - { - // Verify the copied node's parent is null (it's been copied to content root) - Assert.IsNull(copiedItemParentKey); - } - else - { - Assert.IsNotNull(copiedItemParentKey); - } - + Assert.IsNotNull(copiedItemParentKey); Assert.AreEqual(targetParentKey, copiedItemParentKey); Assert.AreNotEqual(sourceParentKey, copiedItemParentKey); }); } + + [Test] + public async Task Structure_Updates_When_Copying_Content_To_Root() + { + // Arrange + DocumentNavigationQueryService.TryGetParentKey(Grandchild2.Key, out Guid? sourceParentKey); + DocumentNavigationQueryService.TryGetSiblingsKeys(Root.Key, out IEnumerable beforeCopyRootSiblingsKeys); + var initialRootSiblingsCount = beforeCopyRootSiblingsKeys.Count(); + + // Act + var copyAttempt = await ContentEditingService.CopyAsync(Grandchild2.Key, null, false, false, Constants.Security.SuperUserKey); + Guid copiedItemKey = copyAttempt.Result.Key; + + // Assert + Assert.AreNotEqual(Grandchild2.Key, copiedItemKey); + + DocumentNavigationQueryService.TryGetParentKey(copiedItemKey, out Guid? copiedItemParentKey); + DocumentNavigationQueryService.TryGetSiblingsKeys(Root.Key, out IEnumerable afterCopyRootSiblingsKeys); + DocumentNavigationQueryService.TryGetChildrenKeys(sourceParentKey.Value, out IEnumerable sourceParentChildrenKeys); + List rootSiblingsList = afterCopyRootSiblingsKeys.ToList(); + + Assert.Multiple(() => + { + // Verifies that the node actually has been copied + Assert.AreNotEqual(sourceParentKey, copiedItemParentKey); + Assert.IsNull(copiedItemParentKey); + + // Verifies that the siblings amount has been updated after copying + Assert.AreEqual(initialRootSiblingsCount + 1, rootSiblingsList.Count); + Assert.IsTrue(rootSiblingsList.Contains(copiedItemKey)); + + // Verifies that the node was copied and not moved + Assert.IsTrue(sourceParentChildrenKeys.Contains(Grandchild2.Key)); + }); + } + + [Test] + public async Task Structure_Updates_When_Copying_Content_With_Descendants() + { + // Arrange + DocumentNavigationQueryService.TryGetParentKey(Grandchild3.Key, out Guid? sourceParentKey); + DocumentNavigationQueryService.TryGetDescendantsKeys(Grandchild3.Key, out IEnumerable beforeCopyGrandChild1Descendents); + DocumentNavigationQueryService.TryGetChildrenKeys(Child3.Key, out IEnumerable beforeCopyChild3ChildrenKeys); + var initialChild3ChildrenCount = beforeCopyChild3ChildrenKeys.Count(); + var initialGrandChild1DescendentsCount = beforeCopyGrandChild1Descendents.Count(); + + // Act + var copyAttempt = await ContentEditingService.CopyAsync(Grandchild3.Key, Child3.Key, false, true, Constants.Security.SuperUserKey); + Guid copiedItemKey = copyAttempt.Result.Key; + + // Assert + Assert.AreNotEqual(Grandchild3.Key, copiedItemKey); + + DocumentNavigationQueryService.TryGetParentKey(copiedItemKey, out Guid? copiedItemParentKey); + DocumentNavigationQueryService.TryGetChildrenKeys(Child3.Key, out IEnumerable afterCopyChild3ChildrenKeys); + DocumentNavigationQueryService.TryGetChildrenKeys(copiedItemKey, out IEnumerable afterCopyGrandChild1Descendents); + List child3ChildrenList = afterCopyChild3ChildrenKeys.ToList(); + List grandChild1DescendantsList = afterCopyGrandChild1Descendents.ToList(); + + // Retrieves the child of the copied item to check its content + var copiedGreatGrandChild1 = await ContentEditingService.GetAsync(grandChild1DescendantsList.First()); + + Assert.Multiple(() => + { + // Verifies that the node actually has been copied + Assert.AreNotEqual(sourceParentKey, copiedItemParentKey); + Assert.AreEqual(Child3.Key, copiedItemParentKey); + Assert.AreEqual(initialChild3ChildrenCount + 1, child3ChildrenList.Count); + + // Verifies that the descendant amount is the same for the original and the moved GrandChild1 node + Assert.AreEqual(initialGrandChild1DescendentsCount, grandChild1DescendantsList.Count); + + // Verifies that the keys are not the same + Assert.AreEqual(GreatGrandchild1.Name, copiedGreatGrandChild1.Name); + Assert.AreNotEqual(GreatGrandchild1.Key, copiedGreatGrandChild1.Key); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Create.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Create.cs index 2ee6c7cabe..4138f80b07 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Create.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Create.cs @@ -7,18 +7,12 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class DocumentNavigationServiceTests { [Test] - public async Task Structure_Updates_When_Creating_Content() + public async Task Structure_Updates_When_Creating_Content_At_Root() { // Arrange DocumentNavigationQueryService.TryGetSiblingsKeys(Root.Key, out IEnumerable initialSiblingsKeys); var initialRootNodeSiblingsCount = initialSiblingsKeys.Count(); - - var createModel = new ContentCreateModel - { - ContentTypeKey = ContentType.Key, - ParentKey = Constants.System.RootKey, // Create node at content root - InvariantName = "Root 2", - }; + var createModel = CreateContentCreateModel("Root 2", Guid.NewGuid(), Constants.System.RootKey); // Act var createAttempt = await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); @@ -36,4 +30,29 @@ public partial class DocumentNavigationServiceTests Assert.AreEqual(createdItemKey, siblingsList.First()); }); } + + [Test] + public async Task Structure_Updates_When_Creating_Child_Content() + { + // Arrange + DocumentNavigationQueryService.TryGetChildrenKeys(Child1.Key, out IEnumerable initialChildrenKeys); + var initialChild1ChildrenCount = initialChildrenKeys.Count(); + var createModel = CreateContentCreateModel("Child1Child", Guid.NewGuid(), Child1.Key); + + // Act + var createAttempt = await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Guid createdItemKey = createAttempt.Result.Content!.Key; + + // Verify that the structure has updated by checking the children of the Child1 node once again + DocumentNavigationQueryService.TryGetChildrenKeys(Child1.Key, out IEnumerable updatedChildrenKeys); + List childrenList = updatedChildrenKeys.ToList(); + + // Assert + Assert.Multiple(() => + { + Assert.IsNotEmpty(childrenList); + Assert.AreEqual(initialChild1ChildrenCount + 1, childrenList.Count); + Assert.IsTrue(childrenList.Contains(createdItemKey)); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Delete.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Delete.cs index 5e6e655d74..8a60eaecc8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Delete.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Delete.cs @@ -3,7 +3,6 @@ using Umbraco.Cms.Core; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; -// TODO: Test that the descendants of the node have also been removed from both structures public partial class DocumentNavigationServiceTests { [Test] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.DeleteFromRecycleBin.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.DeleteFromRecycleBin.cs index 1f9b819366..ee9f2815aa 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.DeleteFromRecycleBin.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.DeleteFromRecycleBin.cs @@ -3,7 +3,6 @@ using Umbraco.Cms.Core; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; -// TODO: check that the descendants have also been removed from both structures - navigation and trash public partial class DocumentNavigationServiceTests { [Test] @@ -12,11 +11,14 @@ public partial class DocumentNavigationServiceTests // Arrange Guid nodeToDelete = Child1.Key; Guid nodeInRecycleBin = Grandchild4.Key; + DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToDelete, out IEnumerable initialDescendantsKeys); // Move nodes to recycle bin - await ContentEditingService.MoveToRecycleBinAsync(nodeInRecycleBin, Constants.Security.SuperUserKey); // Make sure we have an item already in the recycle bin to act as a sibling - await ContentEditingService.MoveToRecycleBinAsync(nodeToDelete, Constants.Security.SuperUserKey); // Make sure the item is in the recycle bin + await ContentEditingService.MoveToRecycleBinAsync(nodeInRecycleBin, Constants.Security.SuperUserKey); + await ContentEditingService.MoveToRecycleBinAsync(nodeToDelete, Constants.Security.SuperUserKey); DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable initialSiblingsKeys); + var initialSiblingsCount = initialSiblingsKeys.Count(); + Assert.AreEqual(initialSiblingsCount, 1); // Act await ContentEditingService.DeleteFromRecycleBinAsync(nodeToDelete, Constants.Security.SuperUserKey); @@ -24,7 +26,17 @@ public partial class DocumentNavigationServiceTests // Assert DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable updatedSiblingsKeys); - // Verify siblings count has decreased by one - Assert.AreEqual(initialSiblingsKeys.Count() - 1, updatedSiblingsKeys.Count()); + Assert.Multiple(() => + { + // Verify siblings count has decreased by one + Assert.AreEqual(initialSiblingsCount - 1, updatedSiblingsKeys.Count()); + foreach (Guid descendant in initialDescendantsKeys) + { + var descendantExists = DocumentNavigationQueryService.TryGetParentKey(descendant, out _); + Assert.IsFalse(descendantExists); + var descendantExistsInRecycleBin = DocumentNavigationQueryService.TryGetParentKeyInBin(descendant, out _); + Assert.IsFalse(descendantExistsInRecycleBin); + } + }); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Move.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Move.cs index 078e06de2b..f31f4f7907 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Move.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Move.cs @@ -7,12 +7,17 @@ public partial class DocumentNavigationServiceTests { [Test] [TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", "60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Grandchild 1 to Child 2 - [TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", null)] // Child 3 to content root [TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96", "C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 2 to Child 1 - public async Task Structure_Updates_When_Moving_Content(Guid nodeToMove, Guid? targetParentKey) + public async Task Structure_Updates_When_Moving_Content(Guid nodeToMove, Guid targetParentKey) { // Arrange DocumentNavigationQueryService.TryGetParentKey(nodeToMove, out Guid? originalParentKey); + DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable initialDescendantsKeys); + var beforeMoveDescendants = initialDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetChildrenKeys(originalParentKey.Value, out IEnumerable beforeMoveInitialParentChildrenKeys); + var beforeMoveInitialParentChildren = beforeMoveInitialParentChildrenKeys.ToList(); + DocumentNavigationQueryService.TryGetChildrenKeys(targetParentKey, out IEnumerable beforeMoveTargetParentChildrenKeys); + var beforeMoveTargetParentChildren = beforeMoveTargetParentChildrenKeys.ToList(); // Act var moveAttempt = await ContentEditingService.MoveAsync(nodeToMove, targetParentKey, Constants.Security.SuperUserKey); @@ -21,19 +26,72 @@ public partial class DocumentNavigationServiceTests DocumentNavigationQueryService.TryGetParentKey(moveAttempt.Result!.Key, out Guid? updatedParentKey); // Assert + DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable afterMoveDescendantsKeys); + var afterMoveDescendants = afterMoveDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetChildrenKeys(originalParentKey.Value, out IEnumerable afterMoveInitialParentChildrenKeys); + var afterMoveInitialParentChildren = afterMoveInitialParentChildrenKeys.ToList(); + DocumentNavigationQueryService.TryGetChildrenKeys(targetParentKey, out IEnumerable afterMoveTargetParentChildrenKeys); + var afterMoveTargetParentChildren = afterMoveTargetParentChildrenKeys.ToList(); + Assert.Multiple(() => { - if (targetParentKey is null) - { - Assert.IsNull(updatedParentKey); - } - else - { - Assert.IsNotNull(updatedParentKey); - } - + Assert.IsNotNull(updatedParentKey); Assert.AreNotEqual(originalParentKey, updatedParentKey); Assert.AreEqual(targetParentKey, updatedParentKey); + + // Verifies that the parent's children have been updated + Assert.AreEqual(beforeMoveInitialParentChildren.Count - 1, afterMoveInitialParentChildren.Count); + Assert.AreEqual(beforeMoveTargetParentChildren.Count + 1, afterMoveTargetParentChildren.Count); + + // Verifies that the descendants are the same before and after the move + Assert.AreEqual(beforeMoveDescendants.Count, afterMoveDescendants.Count); + Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants); + }); + } + + [Test] + public async Task Structure_Updates_When_Moving_Content_To_Root() + { + // Arrange + Guid nodeToMove = Child3.Key; + Guid? targetParentKey = Constants.System.RootKey; // Root + DocumentNavigationQueryService.TryGetParentKey(nodeToMove, out Guid? originalParentKey); + DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable initialDescendantsKeys); + var beforeMoveDescendants = initialDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetDescendantsKeys(originalParentKey.Value, out IEnumerable beforeMoveInitialParentDescendantsKeys); + var beforeMoveInitialParentDescendants = beforeMoveInitialParentDescendantsKeys.ToList(); + + // The Root node is the only node at the root + DocumentNavigationQueryService.TryGetSiblingsKeys(Root.Key, out IEnumerable beforeMoveSiblingsKeys); + var beforeMoveRootSiblings = beforeMoveSiblingsKeys.ToList(); + + // Act + var moveAttempt = await ContentEditingService.MoveAsync(nodeToMove, targetParentKey, Constants.Security.SuperUserKey); + + // Verify the node's new parent is updated + DocumentNavigationQueryService.TryGetParentKey(moveAttempt.Result!.Key, out Guid? updatedParentKey); + + // Assert + DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable afterMoveDescendantsKeys); + var afterMoveDescendants = afterMoveDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetDescendantsKeys((Guid)originalParentKey, out IEnumerable afterMoveInitialParentDescendantsKeys); + var afterMoveInitialParentDescendants = afterMoveInitialParentDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetSiblingsKeys(Root.Key, out IEnumerable afterMoveSiblingsKeys); + var afterMoveRootSiblings = afterMoveSiblingsKeys.ToList(); + + Assert.Multiple(() => + { + Assert.IsNull(updatedParentKey); + Assert.AreNotEqual(originalParentKey, updatedParentKey); + Assert.AreEqual(targetParentKey, updatedParentKey); + + // Verifies that the parent's children have been updated + Assert.AreEqual(beforeMoveInitialParentDescendants.Count - (afterMoveDescendants.Count + 1), afterMoveInitialParentDescendants.Count); + Assert.AreEqual(beforeMoveRootSiblings.Count + 1, afterMoveRootSiblings.Count); + + // Verifies that the descendants are the same before and after the move + Assert.AreEqual(beforeMoveDescendants.Count, afterMoveDescendants.Count); + Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants); }); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.MoveToRecycleBin.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.MoveToRecycleBin.cs index 1bd4bd9d83..9e5e7dcc69 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.MoveToRecycleBin.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.MoveToRecycleBin.cs @@ -3,8 +3,6 @@ using Umbraco.Cms.Core; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; -// TODO: also check that initial siblings count's decreased -// TODO: and that descendants are still the same (i.e. they've also been moved to recycle bin) public partial class DocumentNavigationServiceTests { [Test] @@ -12,7 +10,16 @@ public partial class DocumentNavigationServiceTests { // Arrange Guid nodeToMoveToRecycleBin = Child3.Key; + Guid nodeInRecycleBin = Grandchild4.Key; + await ContentEditingService.MoveToRecycleBinAsync(nodeInRecycleBin, Constants.Security.SuperUserKey); + DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable initialSiblingsKeys); + var beforeMoveRecycleBinSiblingsCount = initialSiblingsKeys.Count(); + Assert.AreEqual(beforeMoveRecycleBinSiblingsCount, 0); DocumentNavigationQueryService.TryGetParentKey(nodeToMoveToRecycleBin, out Guid? originalParentKey); + DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToMoveToRecycleBin, out IEnumerable initialDescendantsKeys); + var beforeMoveDescendants = initialDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetChildrenKeys(originalParentKey.Value, out IEnumerable initialParentChildrenKeys); + var beforeMoveParentSiblingsCount = initialParentChildrenKeys.Count(); // Act await ContentEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey); @@ -20,13 +27,23 @@ public partial class DocumentNavigationServiceTests // Assert var nodeExists = DocumentNavigationQueryService.TryGetParentKey(nodeToMoveToRecycleBin, out _); // Verify that the item is no longer in the document structure var nodeExistsInRecycleBin = DocumentNavigationQueryService.TryGetParentKeyInBin(nodeToMoveToRecycleBin, out Guid? updatedParentKeyInRecycleBin); + DocumentNavigationQueryService.TryGetDescendantsKeysInBin(nodeToMoveToRecycleBin, out IEnumerable afterMoveDescendantsKeys); + var afterMoveDescendants = afterMoveDescendantsKeys.ToList(); + DocumentNavigationQueryService.TryGetChildrenKeys((Guid)originalParentKey, out IEnumerable afterMoveParentChildrenKeys); + var afterMoveParentSiblingsCount = afterMoveParentChildrenKeys.Count(); + DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable afterMoveRecycleBinSiblingsKeys); + var afterMoveRecycleBinSiblingsCount = afterMoveRecycleBinSiblingsKeys.Count(); Assert.Multiple(() => { Assert.IsFalse(nodeExists); Assert.IsTrue(nodeExistsInRecycleBin); Assert.AreNotEqual(originalParentKey, updatedParentKeyInRecycleBin); + Assert.IsNull(updatedParentKeyInRecycleBin); // Verify the node's parent is now located at the root of the recycle bin (null) + Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants); + Assert.AreEqual(beforeMoveParentSiblingsCount - 1, afterMoveParentSiblingsCount); + Assert.AreEqual(beforeMoveRecycleBinSiblingsCount + 1, afterMoveRecycleBinSiblingsCount); }); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Rebuild.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Rebuild.cs index 6d70870a32..addf668cb3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Rebuild.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Rebuild.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Navigation; @@ -13,14 +14,14 @@ public partial class DocumentNavigationServiceTests // Arrange Guid nodeKey = Root.Key; - // Capture original built state of DocumentNavigationService + // Capture original built state of DocumentNavigationQueryService DocumentNavigationQueryService.TryGetParentKey(nodeKey, out Guid? originalParentKey); DocumentNavigationQueryService.TryGetChildrenKeys(nodeKey, out IEnumerable originalChildrenKeys); DocumentNavigationQueryService.TryGetDescendantsKeys(nodeKey, out IEnumerable originalDescendantsKeys); DocumentNavigationQueryService.TryGetAncestorsKeys(nodeKey, out IEnumerable originalAncestorsKeys); DocumentNavigationQueryService.TryGetSiblingsKeys(nodeKey, out IEnumerable originalSiblingsKeys); - // Im-memory navigation structure is empty here + // In-memory navigation structure is empty here var newDocumentNavigationService = new DocumentNavigationService(GetRequiredService(), GetRequiredService()); var initialNodeExists = newDocumentNavigationService.TryGetParentKey(nodeKey, out _); @@ -52,8 +53,47 @@ public partial class DocumentNavigationServiceTests } [Test] - // TODO: Test that you can rebuild bin structure as well public async Task Bin_Structure_Can_Rebuild() { + // Arrange + Guid nodeKey = Root.Key; + await ContentEditingService.MoveToRecycleBinAsync(nodeKey, Constants.Security.SuperUserKey); + + // Capture original built state of DocumentNavigationQueryService + DocumentNavigationQueryService.TryGetParentKeyInBin(nodeKey, out Guid? originalParentKey); + DocumentNavigationQueryService.TryGetChildrenKeysInBin(nodeKey, out IEnumerable originalChildrenKeys); + DocumentNavigationQueryService.TryGetDescendantsKeysInBin(nodeKey, out IEnumerable originalDescendantsKeys); + DocumentNavigationQueryService.TryGetAncestorsKeysInBin(nodeKey, out IEnumerable originalAncestorsKeys); + DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeKey, out IEnumerable originalSiblingsKeys); + + // In-memory navigation structure is empty here + var newDocumentNavigationService = new DocumentNavigationService(GetRequiredService(), GetRequiredService()); + var initialNodeExists = newDocumentNavigationService.TryGetParentKeyInBin(nodeKey, out _); + + // Act + await newDocumentNavigationService.RebuildBinAsync(); + + // Capture rebuilt state + var nodeExists = newDocumentNavigationService.TryGetParentKeyInBin(nodeKey, out Guid? parentKeyFromRebuild); + newDocumentNavigationService.TryGetChildrenKeysInBin(nodeKey, out IEnumerable childrenKeysFromRebuild); + newDocumentNavigationService.TryGetDescendantsKeysInBin(nodeKey, out IEnumerable descendantsKeysFromRebuild); + newDocumentNavigationService.TryGetAncestorsKeysInBin(nodeKey, out IEnumerable ancestorsKeysFromRebuild); + newDocumentNavigationService.TryGetSiblingsKeysInBin(nodeKey, out IEnumerable siblingsKeysFromRebuild); + + // Assert + Assert.Multiple(() => + { + Assert.IsFalse(initialNodeExists); + + // Verify that the item is present in the navigation structure after a rebuild + Assert.IsTrue(nodeExists); + + // Verify that we have the same items as in the original built state of DocumentNavigationService + Assert.AreEqual(originalParentKey, parentKeyFromRebuild); + CollectionAssert.AreEquivalent(originalChildrenKeys, childrenKeysFromRebuild); + CollectionAssert.AreEquivalent(originalDescendantsKeys, descendantsKeysFromRebuild); + CollectionAssert.AreEquivalent(originalAncestorsKeys, ancestorsKeysFromRebuild); + CollectionAssert.AreEquivalent(originalSiblingsKeys, siblingsKeysFromRebuild); + }); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Restore.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Restore.cs index 3151fb83e4..e57f0c652c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Restore.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/DocumentNavigationServiceTests.Restore.cs @@ -3,7 +3,6 @@ using Umbraco.Cms.Core; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; -// TODO: test that descendants are also restored in the right place public partial class DocumentNavigationServiceTests { [Test] @@ -20,6 +19,8 @@ public partial class DocumentNavigationServiceTests await ContentEditingService.MoveToRecycleBinAsync(nodeToRestore, Constants.Security.SuperUserKey); // Make sure the item is in the recycle bin DocumentNavigationQueryService.TryGetParentKeyInBin(nodeToRestore, out Guid? initialParentKey); DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable initialSiblingsKeys); + DocumentNavigationQueryService.TryGetDescendantsKeysInBin(nodeToRestore, out IEnumerable initialDescendantsKeys); + var beforeRestoreDescendants = initialDescendantsKeys.ToList(); // Act var restoreAttempt = await ContentEditingService.RestoreAsync(nodeToRestore, targetParentKey, Constants.Security.SuperUserKey); @@ -28,7 +29,8 @@ public partial class DocumentNavigationServiceTests // Assert DocumentNavigationQueryService.TryGetParentKey(restoredItemKey, out Guid? restoredItemParentKey); DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable updatedSiblingsKeys); - + DocumentNavigationQueryService.TryGetDescendantsKeys(restoredItemKey, out IEnumerable afterRestoreDescendantsKeys); + var afterRestoreDescendants = afterRestoreDescendantsKeys.ToList(); Assert.Multiple(() => { // Verify siblings count has decreased by one @@ -44,6 +46,7 @@ public partial class DocumentNavigationServiceTests Assert.AreNotEqual(initialParentKey, restoredItemParentKey); } + Assert.AreEqual(beforeRestoreDescendants, afterRestoreDescendants); Assert.AreEqual(targetParentKey, restoredItemParentKey); }); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Create.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Create.cs index 1b3b6551a5..3111e5c8e5 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Create.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Create.cs @@ -5,5 +5,53 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { + [Test] + public async Task Structure_Updates_When_Creating_Media_At_Root() + { + // Arrange + MediaNavigationQueryService.TryGetSiblingsKeys(Album.Key, out IEnumerable initialSiblingsKeys); + var initialRootNodeSiblingsCount = initialSiblingsKeys.Count(); + var createModel = CreateMediaCreateModel("Root Image", Guid.NewGuid(), ImageMediaType.Key); + // Act + var createAttempt = await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Guid createdItemKey = createAttempt.Result.Content!.Key; + + // Verify that the structure has updated by checking the siblings list of the Root once again + MediaNavigationQueryService.TryGetSiblingsKeys(Album.Key, out IEnumerable updatedSiblingsKeys); + List siblingsList = updatedSiblingsKeys.ToList(); + + // Assert + Assert.Multiple(() => + { + Assert.IsNotEmpty(siblingsList); + Assert.AreEqual(initialRootNodeSiblingsCount + 1, siblingsList.Count); + Assert.AreEqual(createdItemKey, siblingsList.First()); + }); + } + + [Test] + public async Task Structure_Updates_When_Creating_Child_Media() + { + // Arrange + MediaNavigationQueryService.TryGetChildrenKeys(Album.Key, out IEnumerable initialChildrenKeys); + var initialChild1ChildrenCount = initialChildrenKeys.Count(); + var createModel = CreateMediaCreateModel("Child Image", Guid.NewGuid(), ImageMediaType.Key, Album.Key); + + // Act + var createAttempt = await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey); + Guid createdItemKey = createAttempt.Result.Content!.Key; + + // Verify that the structure has updated by checking the children of the Child1 node once again + MediaNavigationQueryService.TryGetChildrenKeys(Album.Key, out IEnumerable updatedChildrenKeys); + List childrenList = updatedChildrenKeys.ToList(); + + // Assert + Assert.Multiple(() => + { + Assert.IsNotEmpty(childrenList); + Assert.AreEqual(initialChild1ChildrenCount + 1, childrenList.Count); + Assert.IsTrue(childrenList.Contains(createdItemKey)); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Delete.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Delete.cs index 1b3b6551a5..5eeadb2484 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Delete.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Delete.cs @@ -5,5 +5,36 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { + [Test] + [TestCase("1CD97C02-8534-4B72-AE9E-AE52EC94CF31")] // Album + [TestCase("DBCAFF2F-BFA4-4744-A948-C290C432D564")] // Sub-album 2 + [TestCase("3E489C32-9315-42DA-95CE-823D154B09C8")] // Image 2 + public async Task Structure_Updates_When_Deleting_Media(Guid nodeToDelete) + { + // Arrange + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToDelete, out IEnumerable initialDescendantsKeys); + // Act + // Deletes the item whether it is in the recycle bin or not + var deleteAttempt = await MediaEditingService.DeleteAsync(nodeToDelete, Constants.Security.SuperUserKey); + Guid deletedItemKey = deleteAttempt.Result.Key; + + // Assert + var nodeExists = MediaNavigationQueryService.TryGetDescendantsKeys(deletedItemKey, out _); + var nodeExistsInRecycleBin = MediaNavigationQueryService.TryGetDescendantsKeysInBin(nodeToDelete, out _); + + Assert.Multiple(() => + { + Assert.AreEqual(nodeToDelete, deletedItemKey); + Assert.IsFalse(nodeExists); + Assert.IsFalse(nodeExistsInRecycleBin); + foreach (Guid descendant in initialDescendantsKeys) + { + var descendantExists = MediaNavigationQueryService.TryGetParentKey(descendant, out _); + Assert.IsFalse(descendantExists); + var descendantExistsInRecycleBin = MediaNavigationQueryService.TryGetParentKeyInBin(descendant, out _); + Assert.IsFalse(descendantExistsInRecycleBin); + } + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.DeleteFromRecycleBin.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.DeleteFromRecycleBin.cs index 1b3b6551a5..c5a69b30f3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.DeleteFromRecycleBin.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.DeleteFromRecycleBin.cs @@ -5,5 +5,38 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { + [Test] + public async Task Structure_Updates_When_Deleting_From_Recycle_Bin() + { + // Arrange + Guid nodeToDelete = Image1.Key; + Guid nodeInRecycleBin = Image2.Key; + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToDelete, out IEnumerable initialDescendantsKeys); + // Move nodes to recycle bin + await MediaEditingService.MoveToRecycleBinAsync(nodeInRecycleBin, Constants.Security.SuperUserKey); + await MediaEditingService.MoveToRecycleBinAsync(nodeToDelete, Constants.Security.SuperUserKey); + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable initialSiblingsKeys); + var initialSiblingsCount = initialSiblingsKeys.Count(); + Assert.AreEqual(initialSiblingsCount, 1); + + // Act + await MediaEditingService.DeleteFromRecycleBinAsync(nodeToDelete, Constants.Security.SuperUserKey); + + // Assert + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable updatedSiblingsKeys); + + Assert.Multiple(() => + { + // Verify siblings count has decreased by one + Assert.AreEqual(initialSiblingsCount - 1, updatedSiblingsKeys.Count()); + foreach (Guid descendant in initialDescendantsKeys) + { + var descendantExists = MediaNavigationQueryService.TryGetParentKey(descendant, out _); + Assert.IsFalse(descendantExists); + var descendantExistsInRecycleBin = MediaNavigationQueryService.TryGetParentKeyInBin(descendant, out _); + Assert.IsFalse(descendantExistsInRecycleBin); + } + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Move.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Move.cs index 1b3b6551a5..1677dcec45 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Move.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Move.cs @@ -5,5 +5,93 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { + [Test] + [TestCase("62BCE72F-8C18-420E-BCAC-112B5ECC95FD", "139DC977-E50F-4382-9728-B278C4B7AC6A")] // Image 4 to Sub-album 1 + [TestCase("E0B23D56-9A0E-4FC4-BD42-834B73B4C7AB", "1CD97C02-8534-4B72-AE9E-AE52EC94CF31")] // Sub-sub-album 1 to Album + public async Task Structure_Updates_When_Moving_Media(Guid nodeToMove, Guid targetParentKey) + { + // Arrange + MediaNavigationQueryService.TryGetParentKey(nodeToMove, out Guid? originalParentKey); + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable initialDescendantsKeys); + var beforeMoveDescendants = initialDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetDescendantsKeys(originalParentKey.Value, out IEnumerable beforeMoveInitialParentDescendantsKeys); + var beforeMoveInitialParentDescendants = beforeMoveInitialParentDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetChildrenKeys(targetParentKey, out IEnumerable beforeMoveTargetParentChildrenKeys); + var beforeMoveTargetParentChildren = beforeMoveTargetParentChildrenKeys.ToList(); + // Act + var moveAttempt = await MediaEditingService.MoveAsync(nodeToMove, targetParentKey, Constants.Security.SuperUserKey); + + // Verify the node's new parent is updated + MediaNavigationQueryService.TryGetParentKey(moveAttempt.Result!.Key, out Guid? updatedParentKey); + + // Assert + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable afterMoveDescendantsKeys); + var afterMoveDescendants = afterMoveDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetDescendantsKeys(originalParentKey.Value, out IEnumerable afterMoveInitialParentDescendantsKeys); + var afterMoveInitialParentDescendants = afterMoveInitialParentDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetChildrenKeys(targetParentKey, out IEnumerable afterMoveTargetParentChildrenKeys); + var afterMoveTargetParentChildren = afterMoveTargetParentChildrenKeys.ToList(); + + Assert.Multiple(() => + { + Assert.IsNotNull(updatedParentKey); + Assert.AreNotEqual(originalParentKey, updatedParentKey); + Assert.AreEqual(targetParentKey, updatedParentKey); + + // Verifies that the parent's children have been updated + Assert.AreEqual(beforeMoveInitialParentDescendants.Count - (afterMoveDescendants.Count + 1), afterMoveInitialParentDescendants.Count); + Assert.AreEqual(beforeMoveTargetParentChildren.Count + 1, afterMoveTargetParentChildren.Count); + + // Verifies that the descendants are the same before and after the move + Assert.AreEqual(beforeMoveDescendants.Count, afterMoveDescendants.Count); + Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants); + }); + } + + [Test] + public async Task Structure_Updates_When_Moving_Media_To_Root() + { + // Arrange + Guid nodeToMove = SubAlbum2.Key; + Guid? targetParentKey = Constants.System.RootKey; + MediaNavigationQueryService.TryGetParentKey(nodeToMove, out Guid? originalParentKey); + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable initialDescendantsKeys); + var beforeMoveDescendants = initialDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetDescendantsKeys(originalParentKey.Value, out IEnumerable beforeMoveInitialParentDescendantsKeys); + var beforeMoveInitialParentDescendants = beforeMoveInitialParentDescendantsKeys.ToList(); + + // The Root node is the only node at the root + MediaNavigationQueryService.TryGetSiblingsKeys(Album.Key, out IEnumerable beforeMoveSiblingsKeys); + var beforeMoveRootSiblings = beforeMoveSiblingsKeys.ToList(); + + // Act + var moveAttempt = await MediaEditingService.MoveAsync(nodeToMove, targetParentKey, Constants.Security.SuperUserKey); + + // Verify the node's new parent is updated + MediaNavigationQueryService.TryGetParentKey(moveAttempt.Result!.Key, out Guid? updatedParentKey); + + // Assert + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToMove, out IEnumerable afterMoveDescendantsKeys); + var afterMoveDescendants = afterMoveDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetDescendantsKeys((Guid)originalParentKey, out IEnumerable afterMoveInitialParentDescendantsKeys); + var afterMoveInitialParentDescendants = afterMoveInitialParentDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetSiblingsKeys(Album.Key, out IEnumerable afterMoveSiblingsKeys); + var afterMoveRootSiblings = afterMoveSiblingsKeys.ToList(); + + Assert.Multiple(() => + { + Assert.IsNull(updatedParentKey); + Assert.AreNotEqual(originalParentKey, updatedParentKey); + Assert.AreEqual(targetParentKey, updatedParentKey); + + // Verifies that the parent's children have been updated + Assert.AreEqual(beforeMoveInitialParentDescendants.Count - (afterMoveDescendants.Count + 1), afterMoveInitialParentDescendants.Count); + Assert.AreEqual(beforeMoveRootSiblings.Count + 1, afterMoveRootSiblings.Count); + + // Verifies that the descendants are the same before and after the move + Assert.AreEqual(beforeMoveDescendants.Count, afterMoveDescendants.Count); + Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.MoveToRecycleBin.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.MoveToRecycleBin.cs index 1b3b6551a5..f54eae0924 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.MoveToRecycleBin.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.MoveToRecycleBin.cs @@ -5,5 +5,44 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { + [Test] + public async Task Structure_Updates_When_Moving_Media_To_Recycle_Bin() + { + // Arrange + Guid nodeToMoveToRecycleBin = Image3.Key; + Guid nodeInRecycleBin = SubSubAlbum1.Key; + await MediaEditingService.MoveToRecycleBinAsync(nodeInRecycleBin, Constants.Security.SuperUserKey); + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable initialSiblingsKeys); + var beforeMoveRecycleBinSiblingsCount = initialSiblingsKeys.Count(); + Assert.AreEqual(beforeMoveRecycleBinSiblingsCount, 0); + MediaNavigationQueryService.TryGetParentKey(nodeToMoveToRecycleBin, out Guid? originalParentKey); + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToMoveToRecycleBin, out IEnumerable initialDescendantsKeys); + var beforeMoveDescendants = initialDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetChildrenKeys(originalParentKey.Value, out IEnumerable initialParentChildrenKeys); + var beforeMoveParentSiblingsCount = initialParentChildrenKeys.Count(); + // Act + await MediaEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey); + + // Assert + var nodeExists = MediaNavigationQueryService.TryGetParentKey(nodeToMoveToRecycleBin, out _); // Verify that the item is no longer in the document structure + var nodeExistsInRecycleBin = MediaNavigationQueryService.TryGetParentKeyInBin(nodeToMoveToRecycleBin, out Guid? updatedParentKeyInRecycleBin); + MediaNavigationQueryService.TryGetDescendantsKeysInBin(nodeToMoveToRecycleBin, out IEnumerable afterMoveDescendantsKeys); + var afterMoveDescendants = afterMoveDescendantsKeys.ToList(); + MediaNavigationQueryService.TryGetChildrenKeys(originalParentKey.Value, out IEnumerable afterMoveParentChildrenKeys); + var afterMoveParentSiblingsCount = afterMoveParentChildrenKeys.Count(); + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable afterMoveRecycleBinSiblingsKeys); + var afterMoveRecycleBinSiblingsCount = afterMoveRecycleBinSiblingsKeys.Count(); + + Assert.Multiple(() => + { + Assert.IsFalse(nodeExists); + Assert.IsTrue(nodeExistsInRecycleBin); + Assert.AreNotEqual(originalParentKey, updatedParentKeyInRecycleBin); + Assert.IsNull(updatedParentKeyInRecycleBin); // Verify the node's parent is now located at the root of the recycle bin (null) + Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants); + Assert.AreEqual(beforeMoveParentSiblingsCount - 1, afterMoveParentSiblingsCount); + Assert.AreEqual(beforeMoveRecycleBinSiblingsCount + 1, afterMoveRecycleBinSiblingsCount); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Rebuild.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Rebuild.cs index 0f36e8b8ec..65244743e3 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Rebuild.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Rebuild.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services.Navigation; @@ -13,14 +14,14 @@ public partial class MediaNavigationServiceTests // Arrange Guid nodeKey = Album.Key; - // Capture original built state of MediaNavigationService + // Capture original built state of MediaNavigationQueryService MediaNavigationQueryService.TryGetParentKey(nodeKey, out Guid? originalParentKey); MediaNavigationQueryService.TryGetChildrenKeys(nodeKey, out IEnumerable originalChildrenKeys); MediaNavigationQueryService.TryGetDescendantsKeys(nodeKey, out IEnumerable originalDescendantsKeys); MediaNavigationQueryService.TryGetAncestorsKeys(nodeKey, out IEnumerable originalAncestorsKeys); MediaNavigationQueryService.TryGetSiblingsKeys(nodeKey, out IEnumerable originalSiblingsKeys); - // Im-memory navigation structure is empty here + // In-memory navigation structure is empty here var newMediaNavigationService = new MediaNavigationService(GetRequiredService(), GetRequiredService()); var initialNodeExists = newMediaNavigationService.TryGetParentKey(nodeKey, out _); @@ -52,8 +53,47 @@ public partial class MediaNavigationServiceTests } [Test] - // TODO: Test that you can rebuild bin structure as well public async Task Bin_Structure_Can_Rebuild() { + // Arrange + Guid nodeKey = Album.Key; + await MediaEditingService.MoveToRecycleBinAsync(nodeKey, Constants.Security.SuperUserKey); + + // Capture original built state of MediaNavigationQueryService + MediaNavigationQueryService.TryGetParentKeyInBin(nodeKey, out Guid? originalParentKey); + MediaNavigationQueryService.TryGetChildrenKeysInBin(nodeKey, out IEnumerable originalChildrenKeys); + MediaNavigationQueryService.TryGetDescendantsKeysInBin(nodeKey, out IEnumerable originalDescendantsKeys); + MediaNavigationQueryService.TryGetAncestorsKeysInBin(nodeKey, out IEnumerable originalAncestorsKeys); + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeKey, out IEnumerable originalSiblingsKeys); + + // In-memory navigation structure is empty here + var newMediaNavigationService = new MediaNavigationService(GetRequiredService(), GetRequiredService()); + var initialNodeExists = newMediaNavigationService.TryGetParentKeyInBin(nodeKey, out _); + + // Act + await newMediaNavigationService.RebuildBinAsync(); + + // Capture rebuilt state + var nodeExists = newMediaNavigationService.TryGetParentKeyInBin(nodeKey, out Guid? parentKeyFromRebuild); + newMediaNavigationService.TryGetChildrenKeysInBin(nodeKey, out IEnumerable childrenKeysFromRebuild); + newMediaNavigationService.TryGetDescendantsKeysInBin(nodeKey, out IEnumerable descendantsKeysFromRebuild); + newMediaNavigationService.TryGetAncestorsKeysInBin(nodeKey, out IEnumerable ancestorsKeysFromRebuild); + newMediaNavigationService.TryGetSiblingsKeysInBin(nodeKey, out IEnumerable siblingsKeysFromRebuild); + + // Assert + Assert.Multiple(() => + { + Assert.IsFalse(initialNodeExists); + + // Verify that the item is present in the navigation structure after a rebuild + Assert.IsTrue(nodeExists); + + // Verify that we have the same items as in the original built state of MediaNavigationService + Assert.AreEqual(originalParentKey, parentKeyFromRebuild); + CollectionAssert.AreEquivalent(originalChildrenKeys, childrenKeysFromRebuild); + CollectionAssert.AreEquivalent(originalDescendantsKeys, descendantsKeysFromRebuild); + CollectionAssert.AreEquivalent(originalAncestorsKeys, ancestorsKeysFromRebuild); + CollectionAssert.AreEquivalent(originalSiblingsKeys, siblingsKeysFromRebuild); + }); } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Restore.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Restore.cs index 1b3b6551a5..22e5e3d799 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Restore.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Restore.cs @@ -5,5 +5,49 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { + [Test] + [TestCase("62BCE72F-8C18-420E-BCAC-112B5ECC95FD", "139DC977-E50F-4382-9728-B278C4B7AC6A")] // Image 4 to Sub-album 1 + [TestCase("DBCAFF2F-BFA4-4744-A948-C290C432D564", "1CD97C02-8534-4B72-AE9E-AE52EC94CF31")] // Sub-album 2 to Album + [TestCase("3E489C32-9315-42DA-95CE-823D154B09C8", null)] // Image 2 to media root + public async Task Structure_Updates_When_Restoring_Media(Guid nodeToRestore, Guid? targetParentKey) + { + // Arrange + Guid nodeInRecycleBin = Image3.Key; + // Move nodes to recycle bin + await MediaEditingService.MoveToRecycleBinAsync(nodeInRecycleBin, Constants.Security.SuperUserKey); + await MediaEditingService.MoveToRecycleBinAsync(nodeToRestore, Constants.Security.SuperUserKey); + MediaNavigationQueryService.TryGetParentKeyInBin(nodeToRestore, out Guid? initialParentKey); + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable initialSiblingsKeys); + MediaNavigationQueryService.TryGetDescendantsKeysInBin(nodeToRestore, out IEnumerable initialDescendantsKeys); + var beforeRestoreDescendants = initialDescendantsKeys.ToList(); + + // Act + var restoreAttempt = await MediaEditingService.RestoreAsync(nodeToRestore, targetParentKey, Constants.Security.SuperUserKey); + Guid restoredItemKey = restoreAttempt.Result.Key; + + // Assert + MediaNavigationQueryService.TryGetParentKey(restoredItemKey, out Guid? restoredItemParentKey); + MediaNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable updatedSiblingsKeys); + MediaNavigationQueryService.TryGetDescendantsKeys(restoredItemKey, out IEnumerable afterRestoreDescendantsKeys); + var afterRestoreDescendants = afterRestoreDescendantsKeys.ToList(); + + Assert.Multiple(() => + { + // Verify siblings count has decreased by one + Assert.AreEqual(initialSiblingsKeys.Count() - 1, updatedSiblingsKeys.Count()); + if (targetParentKey is null) + { + Assert.IsNull(restoredItemParentKey); + } + else + { + Assert.IsNotNull(restoredItemParentKey); + Assert.AreNotEqual(initialParentKey, restoredItemParentKey); + } + + Assert.AreEqual(beforeRestoreDescendants, afterRestoreDescendants); + Assert.AreEqual(targetParentKey, restoredItemParentKey); + }); + } } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Update.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Update.cs index 1b3b6551a5..0c24dbd0c8 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Update.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/MediaNavigationServiceTests.Update.cs @@ -1,9 +1,53 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.ContentEditing; namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; public partial class MediaNavigationServiceTests { +[Test] + public async Task Structure_Does_Not_Update_When_Updating_Media() + { + // Arrange + Guid nodeToUpdate = Album.Key; + // Capture initial state + MediaNavigationQueryService.TryGetParentKey(nodeToUpdate, out Guid? initialParentKey); + MediaNavigationQueryService.TryGetChildrenKeys(nodeToUpdate, out IEnumerable initialChildrenKeys); + MediaNavigationQueryService.TryGetDescendantsKeys(nodeToUpdate, out IEnumerable initialDescendantsKeys); + MediaNavigationQueryService.TryGetAncestorsKeys(nodeToUpdate, out IEnumerable initialAncestorsKeys); + MediaNavigationQueryService.TryGetSiblingsKeys(nodeToUpdate, out IEnumerable initialSiblingsKeys); + + var updateModel = new MediaUpdateModel + { + InvariantName = "Updated Album", + }; + + // Act + var updateAttempt = await MediaEditingService.UpdateAsync(nodeToUpdate, updateModel, Constants.Security.SuperUserKey); + Guid updatedItemKey = updateAttempt.Result.Content!.Key; + + // Capture updated state + var nodeExists = MediaNavigationQueryService.TryGetParentKey(updatedItemKey, out Guid? updatedParentKey); + MediaNavigationQueryService.TryGetChildrenKeys(updatedItemKey, out IEnumerable childrenKeysAfterUpdate); + MediaNavigationQueryService.TryGetDescendantsKeys(updatedItemKey, out IEnumerable descendantsKeysAfterUpdate); + MediaNavigationQueryService.TryGetAncestorsKeys(updatedItemKey, out IEnumerable ancestorsKeysAfterUpdate); + MediaNavigationQueryService.TryGetSiblingsKeys(updatedItemKey, out IEnumerable siblingsKeysAfterUpdate); + + // Assert + Assert.Multiple(() => + { + // Verify that the item is still present in the navigation structure + Assert.IsTrue(nodeExists); + Assert.AreEqual(nodeToUpdate, updatedItemKey); + + // Verify that nothing's changed + Assert.AreEqual(initialParentKey, updatedParentKey); + CollectionAssert.AreEquivalent(initialChildrenKeys, childrenKeysAfterUpdate); + CollectionAssert.AreEquivalent(initialDescendantsKeys, descendantsKeysAfterUpdate); + CollectionAssert.AreEquivalent(initialAncestorsKeys, ancestorsKeysAfterUpdate); + CollectionAssert.AreEquivalent(initialSiblingsKeys, siblingsKeysAfterUpdate); + }); + } }