V15: Implement sorting for the in-memory navigation structures (document and media) (#17280)
* bump version to 15.1.0 * V15 Fixed the failing smoke tests in the pipeline v15 (#17158) * Fixed the failing tests of Member Group due to UI changes * Fixed the failing tests of Member due to UI changes * Fixed the failing tests of User due to UI changes * Fixed failing tests for Dictionary and Document Type * Updated tests due to test helper changes * Bumped version * Updated assert steps due to the response changes * Updated tests due to api helper changes * Updated tests due to UI changes * Fixed tests for delete partial view * Fixed tests * Added more waits * Updated assert steps * Fixed failing tests for Block Grid and Media * Added more waits * Added skip tests * Removed waits time * Updated assertion steps for User * Added todo * Updated tests due to api helper changes * Bumped version * Added skip tests * Fetch sortOrder for each navigationNode * Update NavigationNode to have sortOrder and change Parent and Children props to keys instead of NavigationNodes * Consider sortOrder when building the navigation structures * Renaming tests * Adding tests for items being the last in structure when added, moved, etc. * Updating names * Cleanup * Updating cache refreshers with changes due to sorting * Refactoring due to sorting changes and resolving key to NavigationNode * Removing sortOrder params from test as they are calculated automatically * Adding content and media integration tests to test sorting functionality * Adding sortOrder param for special case when adding new nodes * Adding new UpdateSortOrder to INavigationManagementService * Revert "V15 Fixed the failing smoke tests in the pipeline v15 (#17158)" This reverts commit31399c3b15. * Revert "bump version to 15.1.0" This reverts commit5e4d15be* Fix revert * Add sort order when creating media --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Co-authored-by: Nhu Dinh <150406148+nhudinh0309@users.noreply.github.com> Co-authored-by: Bjarke Berg <mail@bergmania.dk>
This commit is contained in:
committed by
GitHub
parent
86557a01cf
commit
44ff8dc5e1
@@ -1,5 +1,3 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
@@ -199,7 +197,7 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
// First creation
|
||||
if (ExistsInNavigation(content.Key) is false && ExistsInNavigationBin(content.Key) is false)
|
||||
{
|
||||
_documentNavigationManagementService.Add(content.Key, GetParentKey(content));
|
||||
_documentNavigationManagementService.Add(content.Key, GetParentKey(content), content.SortOrder);
|
||||
if (content.Trashed)
|
||||
{
|
||||
// If created as trashed, move to bin
|
||||
@@ -215,14 +213,20 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase<ContentCac
|
||||
}
|
||||
else
|
||||
{
|
||||
// It must have been saved. Check if parent is different
|
||||
if (_documentNavigationQueryService.TryGetParentKey(content.Key, out var oldParentKey))
|
||||
if (_documentNavigationQueryService.TryGetParentKey(content.Key, out Guid? oldParentKey) is false)
|
||||
{
|
||||
Guid? newParentKey = GetParentKey(content);
|
||||
if (oldParentKey != newParentKey)
|
||||
{
|
||||
_documentNavigationManagementService.Move(content.Key, newParentKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// It must have been saved. Check if parent is different
|
||||
Guid? newParentKey = GetParentKey(content);
|
||||
if (oldParentKey != newParentKey)
|
||||
{
|
||||
_documentNavigationManagementService.Move(content.Key, newParentKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_documentNavigationManagementService.UpdateSortOrder(content.Key, content.SortOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Umbraco.Cms.Core.Events;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.Changes;
|
||||
@@ -169,7 +168,7 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
// First creation
|
||||
if (ExistsInNavigation(media.Key) is false && ExistsInNavigationBin(media.Key) is false)
|
||||
{
|
||||
_mediaNavigationManagementService.Add(media.Key, GetParentKey(media));
|
||||
_mediaNavigationManagementService.Add(media.Key, GetParentKey(media), media.SortOrder);
|
||||
if (media.Trashed)
|
||||
{
|
||||
// If created as trashed, move to bin
|
||||
@@ -185,14 +184,20 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase<MediaCacheRe
|
||||
}
|
||||
else
|
||||
{
|
||||
// It must have been saved. Check if parent is different
|
||||
if (_mediaNavigationQueryService.TryGetParentKey(media.Key, out var oldParentKey))
|
||||
if (_mediaNavigationQueryService.TryGetParentKey(media.Key, out var oldParentKey) is false)
|
||||
{
|
||||
Guid? newParentKey = GetParentKey(media);
|
||||
if (oldParentKey != newParentKey)
|
||||
{
|
||||
_mediaNavigationManagementService.Move(media.Key, newParentKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// It must have been saved. Check if parent is different
|
||||
Guid? newParentKey = GetParentKey(media);
|
||||
if (oldParentKey != newParentKey)
|
||||
{
|
||||
_mediaNavigationManagementService.Move(media.Key, newParentKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mediaNavigationManagementService.UpdateSortOrder(media.Key, media.SortOrder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ public interface INavigationModel
|
||||
/// </summary>
|
||||
int ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sort order of the entity.
|
||||
/// </summary>
|
||||
int SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this entity is in the recycle bin.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,30 +1,57 @@
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Umbraco.Cms.Core.Models.Navigation;
|
||||
|
||||
public sealed class NavigationNode
|
||||
{
|
||||
private List<NavigationNode> _children;
|
||||
private HashSet<Guid> _children;
|
||||
|
||||
public Guid Key { get; private set; }
|
||||
|
||||
public NavigationNode? Parent { get; private set; }
|
||||
public int SortOrder { get; private set; }
|
||||
|
||||
public IEnumerable<NavigationNode> Children => _children.AsEnumerable();
|
||||
public Guid? Parent { get; private set; }
|
||||
|
||||
public NavigationNode(Guid key)
|
||||
public ISet<Guid> Children => _children;
|
||||
|
||||
public NavigationNode(Guid key, int sortOrder = 0)
|
||||
{
|
||||
Key = key;
|
||||
_children = new List<NavigationNode>();
|
||||
SortOrder = sortOrder;
|
||||
_children = new HashSet<Guid>();
|
||||
}
|
||||
|
||||
public void AddChild(NavigationNode child)
|
||||
public void UpdateSortOrder(int newSortOrder) => SortOrder = newSortOrder;
|
||||
|
||||
public void AddChild(ConcurrentDictionary<Guid, NavigationNode> navigationStructure, Guid childKey)
|
||||
{
|
||||
child.Parent = this;
|
||||
_children.Add(child);
|
||||
if (navigationStructure.TryGetValue(childKey, out NavigationNode? child) is false)
|
||||
{
|
||||
throw new KeyNotFoundException($"Item with key '{childKey}' was not found in the navigation structure.");
|
||||
}
|
||||
|
||||
child.Parent = Key;
|
||||
|
||||
// Add it as the last item
|
||||
child.SortOrder = _children.Count;
|
||||
|
||||
_children.Add(childKey);
|
||||
|
||||
// Update the navigation structure
|
||||
navigationStructure[childKey] = child;
|
||||
}
|
||||
|
||||
public void RemoveChild(NavigationNode child)
|
||||
public void RemoveChild(ConcurrentDictionary<Guid, NavigationNode> navigationStructure, Guid childKey)
|
||||
{
|
||||
_children.Remove(child);
|
||||
if (navigationStructure.TryGetValue(childKey, out NavigationNode? child) is false)
|
||||
{
|
||||
throw new KeyNotFoundException($"Item with key '{childKey}' was not found in the navigation structure.");
|
||||
}
|
||||
|
||||
_children.Remove(childKey);
|
||||
child.Parent = null;
|
||||
|
||||
// Update the navigation structure
|
||||
navigationStructure[childKey] = child;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Umbraco.Cms.Core.Factories;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Navigation;
|
||||
using Umbraco.Cms.Core.Persistence.Repositories;
|
||||
@@ -97,11 +96,13 @@ internal abstract class ContentNavigationServiceBase
|
||||
// Recursively remove all descendants and add them to recycle bin
|
||||
AddDescendantsToRecycleBinRecursively(nodeToRemove);
|
||||
|
||||
// Reset the SortOrder based on its new position in the bin
|
||||
nodeToRemove.UpdateSortOrder(_recycleBinNavigationStructure.Count);
|
||||
return _recycleBinNavigationStructure.TryAdd(nodeToRemove.Key, nodeToRemove) &&
|
||||
_navigationStructure.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
public bool Add(Guid key, Guid? parentKey = null)
|
||||
public bool Add(Guid key, Guid? parentKey = null, int? sortOrder = null)
|
||||
{
|
||||
NavigationNode? parentNode = null;
|
||||
if (parentKey.HasValue)
|
||||
@@ -116,13 +117,14 @@ internal abstract class ContentNavigationServiceBase
|
||||
_roots.Add(key);
|
||||
}
|
||||
|
||||
var newNode = new NavigationNode(key);
|
||||
// Note: sortOrder can't be automatically determined for items at root level, so it needs to be passed in
|
||||
var newNode = new NavigationNode(key, sortOrder ?? 0);
|
||||
if (_navigationStructure.TryAdd(key, newNode) is false)
|
||||
{
|
||||
return false; // Node with this key already exists
|
||||
}
|
||||
|
||||
parentNode?.AddChild(newNode);
|
||||
parentNode?.AddChild(_navigationStructure, key);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -155,13 +157,25 @@ internal abstract class ContentNavigationServiceBase
|
||||
}
|
||||
|
||||
// Remove the node from its current parent's children list
|
||||
if (nodeToMove.Parent is not null && _navigationStructure.TryGetValue(nodeToMove.Parent.Key, out var currentParentNode))
|
||||
if (nodeToMove.Parent is not null && _navigationStructure.TryGetValue(nodeToMove.Parent.Value, out NavigationNode? currentParentNode))
|
||||
{
|
||||
currentParentNode.RemoveChild(nodeToMove);
|
||||
currentParentNode.RemoveChild(_navigationStructure, key);
|
||||
}
|
||||
|
||||
// Set the new parent for the node (if parent node is null - the node is moved to root)
|
||||
targetParentNode?.AddChild(nodeToMove);
|
||||
targetParentNode?.AddChild(_navigationStructure, key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UpdateSortOrder(Guid key, int newSortOrder)
|
||||
{
|
||||
if (_navigationStructure.TryGetValue(key, out NavigationNode? node) is false)
|
||||
{
|
||||
return false; // Node doesn't exist
|
||||
}
|
||||
|
||||
node.UpdateSortOrder(newSortOrder);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -195,8 +209,7 @@ internal abstract class ContentNavigationServiceBase
|
||||
}
|
||||
|
||||
// Set the new parent for the node (if parent node is null - the node is moved to root)
|
||||
targetParentNode?.AddChild(nodeToRestore);
|
||||
|
||||
targetParentNode?.AddChild(_recycleBinNavigationStructure, key);
|
||||
|
||||
// Restore the node and its descendants from the recycle bin to the main structure
|
||||
RestoreNodeAndDescendantsRecursively(nodeToRestore);
|
||||
@@ -240,7 +253,7 @@ internal abstract class ContentNavigationServiceBase
|
||||
{
|
||||
if (structure.TryGetValue(childKey, out NavigationNode? childNode))
|
||||
{
|
||||
parentKey = childNode.Parent?.Key;
|
||||
parentKey = childNode.Parent;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -252,7 +265,10 @@ internal abstract class ContentNavigationServiceBase
|
||||
private bool TryGetRootKeysFromStructure(IList<Guid> input, out IEnumerable<Guid> rootKeys)
|
||||
{
|
||||
// TODO can we make this more efficient?
|
||||
rootKeys = input.ToArray();
|
||||
// Sort by SortOrder
|
||||
rootKeys = input
|
||||
.OrderBy(key => _navigationStructure[key].SortOrder)
|
||||
.ToList();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -266,7 +282,9 @@ internal abstract class ContentNavigationServiceBase
|
||||
return false;
|
||||
}
|
||||
|
||||
childrenKeys = parentNode.Children.Select(child => child.Key);
|
||||
// Keep children keys ordered based on their SortOrder
|
||||
childrenKeys = GetOrderedChildren(parentNode, structure).ToList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -281,7 +299,7 @@ internal abstract class ContentNavigationServiceBase
|
||||
return false;
|
||||
}
|
||||
|
||||
GetDescendantsRecursively(parentNode, descendants);
|
||||
GetDescendantsRecursively(structure, parentNode, descendants);
|
||||
|
||||
descendantsKeys = descendants;
|
||||
return true;
|
||||
@@ -291,17 +309,16 @@ internal abstract class ContentNavigationServiceBase
|
||||
{
|
||||
var ancestors = new List<Guid>();
|
||||
|
||||
if (structure.TryGetValue(childKey, out NavigationNode? childNode) is false)
|
||||
if (structure.TryGetValue(childKey, out NavigationNode? node) is false)
|
||||
{
|
||||
// Child doesn't exist
|
||||
ancestorsKeys = [];
|
||||
return false;
|
||||
}
|
||||
|
||||
while (childNode?.Parent is not null)
|
||||
while (node.Parent is not null && structure.TryGetValue(node.Parent.Value, out node))
|
||||
{
|
||||
ancestors.Add(childNode.Parent.Key);
|
||||
childNode = childNode.Parent;
|
||||
ancestors.Add(node.Key);
|
||||
}
|
||||
|
||||
ancestorsKeys = ancestors;
|
||||
@@ -322,12 +339,13 @@ internal abstract class ContentNavigationServiceBase
|
||||
// To find siblings of a node at root level, we need to iterate over all items and add those with null Parent
|
||||
siblingsKeys = structure
|
||||
.Where(kv => kv.Value.Parent is null && kv.Key != key)
|
||||
.OrderBy(kv => kv.Value.SortOrder)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryGetChildrenKeys(node.Parent.Key, out IEnumerable<Guid> childrenKeys) is false)
|
||||
if (TryGetChildrenKeys(node.Parent.Value, out IEnumerable<Guid> childrenKeys) is false)
|
||||
{
|
||||
return false; // Couldn't retrieve children keys
|
||||
}
|
||||
@@ -337,12 +355,18 @@ internal abstract class ContentNavigationServiceBase
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GetDescendantsRecursively(NavigationNode node, List<Guid> descendants)
|
||||
private void GetDescendantsRecursively(ConcurrentDictionary<Guid, NavigationNode> structure, NavigationNode node, List<Guid> descendants)
|
||||
{
|
||||
foreach (NavigationNode child in node.Children)
|
||||
var childrenKeys = GetOrderedChildren(node, structure).ToList();
|
||||
foreach (Guid childKey in childrenKeys)
|
||||
{
|
||||
descendants.Add(child.Key);
|
||||
GetDescendantsRecursively(child, descendants);
|
||||
descendants.Add(childKey);
|
||||
|
||||
// Retrieve the child node and its descendants
|
||||
if (structure.TryGetValue(childKey, out NavigationNode? childNode))
|
||||
{
|
||||
GetDescendantsRecursively(structure, childNode, descendants);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,9 +378,9 @@ internal abstract class ContentNavigationServiceBase
|
||||
}
|
||||
|
||||
// Remove the node from its parent's children list
|
||||
if (nodeToRemove.Parent is not null && structure.TryGetValue(nodeToRemove.Parent.Key, out NavigationNode? parentNode))
|
||||
if (nodeToRemove.Parent is not null && structure.TryGetValue(nodeToRemove.Parent.Value, out NavigationNode? parentNode))
|
||||
{
|
||||
parentNode.RemoveChild(nodeToRemove);
|
||||
parentNode.RemoveChild(structure, key);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -366,25 +390,39 @@ internal abstract class ContentNavigationServiceBase
|
||||
{
|
||||
_recycleBinRoots.Add(node.Key);
|
||||
_roots.Remove(node.Key);
|
||||
var childrenKeys = GetOrderedChildren(node, _navigationStructure).ToList();
|
||||
|
||||
foreach (NavigationNode child in node.Children)
|
||||
foreach (Guid childKey in childrenKeys)
|
||||
{
|
||||
AddDescendantsToRecycleBinRecursively(child);
|
||||
if (_navigationStructure.TryGetValue(childKey, out NavigationNode? childNode) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset the SortOrder based on its new position in the bin
|
||||
childNode.UpdateSortOrder(_recycleBinNavigationStructure.Count);
|
||||
AddDescendantsToRecycleBinRecursively(childNode);
|
||||
|
||||
// Only remove the child from the main structure if it was successfully added to the recycle bin
|
||||
if (_recycleBinNavigationStructure.TryAdd(child.Key, child))
|
||||
if (_recycleBinNavigationStructure.TryAdd(childKey, childNode))
|
||||
{
|
||||
_navigationStructure.TryRemove(child.Key, out _);
|
||||
_navigationStructure.TryRemove(childKey, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveDescendantsRecursively(NavigationNode node)
|
||||
{
|
||||
foreach (NavigationNode child in node.Children)
|
||||
var childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure).ToList();
|
||||
foreach (Guid childKey in childrenKeys)
|
||||
{
|
||||
RemoveDescendantsRecursively(child);
|
||||
_recycleBinNavigationStructure.TryRemove(child.Key, out _);
|
||||
if (_recycleBinNavigationStructure.TryGetValue(childKey, out NavigationNode? childNode) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RemoveDescendantsRecursively(childNode);
|
||||
_recycleBinNavigationStructure.TryRemove(childKey, out _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,28 +432,41 @@ internal abstract class ContentNavigationServiceBase
|
||||
{
|
||||
_roots.Add(node.Key);
|
||||
}
|
||||
_recycleBinRoots.Remove(node.Key);
|
||||
|
||||
foreach (NavigationNode child in node.Children)
|
||||
_recycleBinRoots.Remove(node.Key);
|
||||
var childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure).ToList();
|
||||
|
||||
foreach (Guid childKey in childrenKeys)
|
||||
{
|
||||
RestoreNodeAndDescendantsRecursively(child);
|
||||
if (_recycleBinNavigationStructure.TryGetValue(childKey, out NavigationNode? childNode) is false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RestoreNodeAndDescendantsRecursively(childNode);
|
||||
|
||||
// Only remove the child from the recycle bin structure if it was successfully added to the main one
|
||||
if (_navigationStructure.TryAdd(child.Key, child))
|
||||
if (_navigationStructure.TryAdd(childKey, childNode))
|
||||
{
|
||||
_recycleBinNavigationStructure.TryRemove(child.Key, out _);
|
||||
_recycleBinNavigationStructure.TryRemove(childKey, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Guid> GetOrderedChildren(NavigationNode node, ConcurrentDictionary<Guid, NavigationNode> structure)
|
||||
=> node.Children
|
||||
.Where(structure.ContainsKey)
|
||||
.OrderBy(childKey => structure[childKey].SortOrder)
|
||||
.ToList();
|
||||
|
||||
private static void BuildNavigationDictionary(ConcurrentDictionary<Guid, NavigationNode> nodesStructure, IList<Guid> roots, IEnumerable<INavigationModel> entities)
|
||||
{
|
||||
var entityList = entities.ToList();
|
||||
IDictionary<int, Guid> idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key);
|
||||
var idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key);
|
||||
|
||||
foreach (INavigationModel entity in entityList)
|
||||
{
|
||||
var node = new NavigationNode(entity.Key);
|
||||
var node = new NavigationNode(entity.Key, entity.SortOrder);
|
||||
nodesStructure[entity.Key] = node;
|
||||
|
||||
// We don't set the parent for items under root, it will stay null
|
||||
@@ -433,7 +484,7 @@ internal abstract class ContentNavigationServiceBase
|
||||
// If the parent node exists in the nodesStructure, add the node to the parent's children (parent is set as well)
|
||||
if (nodesStructure.TryGetValue(parentKey, out NavigationNode? parentNode))
|
||||
{
|
||||
parentNode.AddChild(node);
|
||||
parentNode.AddChild(nodesStructure, entity.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,20 @@ public interface INavigationManagementService
|
||||
/// The unique identifier of the parent node. If <c>null</c>, the new node will be added to
|
||||
/// the root level.
|
||||
/// </param>
|
||||
/// <param name="sortOrder">
|
||||
/// Optional value to define the node's position among its siblings when
|
||||
/// adding node at root level.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the node was successfully added to the main navigation structure;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
bool Add(Guid key, Guid? parentKey = null);
|
||||
/// <remarks>
|
||||
/// The sort order is particularly important when adding nodes at the root level. For child nodes,
|
||||
/// it can usually be determined by the number of existing children under the parent. However,
|
||||
/// when adding nodes directly to the root (where parentKey is null), a sort order must be provided
|
||||
/// to ensure the item appears in the correct position among other root-level items.
|
||||
/// </remarks>
|
||||
bool Add(Guid key, Guid? parentKey = null, int? sortOrder = null);
|
||||
|
||||
/// <summary>
|
||||
/// Moves an existing node to a new parent in the main navigation structure. If a
|
||||
@@ -54,4 +63,15 @@ public interface INavigationManagementService
|
||||
/// in the main navigation structure; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
bool Move(Guid key, Guid? targetParentKey = null);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the sort order of a node in the main navigation structure.
|
||||
/// The sort order of other nodes in the same level will be adjusted accordingly.
|
||||
/// </summary>
|
||||
/// <param name="key">The unique identifier of the node to update.</param>
|
||||
/// <param name="newSortOrder">The new sort order for the node.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the node's sort order was successfully updated; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
bool UpdateSortOrder(Guid key, int newSortOrder);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public interface INavigationQueryService
|
||||
|
||||
bool TryGetDescendantsKeysOrSelfKeys(Guid childKey, out IEnumerable<Guid> descendantsOrSelfKeys)
|
||||
{
|
||||
if(TryGetDescendantsKeys(childKey, out var descendantsKeys))
|
||||
if (TryGetDescendantsKeys(childKey, out IEnumerable<Guid>? descendantsKeys))
|
||||
{
|
||||
descendantsOrSelfKeys = childKey.Yield().Concat(descendantsKeys);
|
||||
return true;
|
||||
@@ -28,14 +28,13 @@ public interface INavigationQueryService
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool TryGetAncestorsKeys(Guid childKey, out IEnumerable<Guid> ancestorsKeys);
|
||||
|
||||
bool TryGetAncestorsOrSelfKeys(Guid childKey, out IEnumerable<Guid> ancestorsOrSelfKeys)
|
||||
{
|
||||
if(TryGetAncestorsKeys(childKey, out var ancestorsKeys))
|
||||
if (TryGetAncestorsKeys(childKey, out IEnumerable<Guid>? ancestorsKeys))
|
||||
{
|
||||
ancestorsOrSelfKeys = childKey.Yield().Concat(ancestorsKeys);
|
||||
ancestorsOrSelfKeys = childKey.Yield().Concat(ancestorsKeys);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ internal class NavigationDto : INavigationModel
|
||||
[Column(NodeDto.ParentIdColumnName)]
|
||||
public int ParentId { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Column(NodeDto.SortOrderColumnName)]
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Column(NodeDto.TrashedColumnName)]
|
||||
public bool Trashed { get; set; }
|
||||
|
||||
@@ -17,6 +17,7 @@ public class NodeDto
|
||||
public const string IdColumnName = "id";
|
||||
public const string KeyColumnName = "uniqueId";
|
||||
public const string ParentIdColumnName = "parentId";
|
||||
public const string SortOrderColumnName = "sortOrder";
|
||||
public const string TrashedColumnName = "trashed";
|
||||
|
||||
private int? _userId;
|
||||
@@ -46,7 +47,7 @@ public class NodeDto
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_Path")]
|
||||
public string Path { get; set; } = null!;
|
||||
|
||||
[Column("sortOrder")]
|
||||
[Column(SortOrderColumnName)]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_" + TableName + "_ObjectType_trashed_sorted", ForColumns = "nodeObjectType,trashed,sortOrder,id", IncludeColumns = "uniqueID,parentID,level,path,nodeUser,text,createDate")]
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
|
||||
@@ -106,4 +106,34 @@ public partial class DocumentNavigationServiceTests
|
||||
Assert.AreNotEqual(GreatGrandchild1.Key, copiedGreatGrandChild1.Key);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Content root
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573")] // Grandchild 1
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF")] // Child 3
|
||||
[TestCase("F381906C-223C-4466-80F7-B63B4EE073F8")] // Grandchild 4
|
||||
public async Task Copying_Content_Adds_It_Last(Guid? parentKey)
|
||||
{
|
||||
// Act
|
||||
var copyAttempt = await ContentEditingService.CopyAsync(Grandchild1.Key, parentKey, false, true, Constants.Security.SuperUserKey);
|
||||
Guid copiedItemKey = copyAttempt.Result.Key;
|
||||
|
||||
// Assert
|
||||
if (parentKey is null)
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(copiedItemKey, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(copiedItemKey, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
@@ -37,7 +36,7 @@ public partial class DocumentNavigationServiceTests
|
||||
// Arrange
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(Child1.Key, out IEnumerable<Guid> initialChildrenKeys);
|
||||
var initialChild1ChildrenCount = initialChildrenKeys.Count();
|
||||
var createModel = CreateContentCreateModel("Child1Child", Guid.NewGuid(), Child1.Key);
|
||||
var createModel = CreateContentCreateModel("Grandchild 3", Guid.NewGuid(), Child1.Key);
|
||||
|
||||
// Act
|
||||
var createAttempt = await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
@@ -55,4 +54,37 @@ public partial class DocumentNavigationServiceTests
|
||||
Assert.IsTrue(childrenList.Contains(createdItemKey));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Content root
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573")] // Grandchild 1
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF")] // Child 3
|
||||
[TestCase("F381906C-223C-4466-80F7-B63B4EE073F8")] // Grandchild 4
|
||||
public async Task Creating_Child_Content_Adds_It_As_The_Last_Child(Guid? parentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid newNodeKey = Guid.NewGuid();
|
||||
var createModel = CreateContentCreateModel("Child", newNodeKey, parentKey);
|
||||
|
||||
// Act
|
||||
await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
if (parentKey is null)
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(newNodeKey, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(newNodeKey, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,42 @@ public partial class DocumentNavigationServiceTests
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Add more test cases
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Siblings_Updates_When_Deleting_Content_And_Adding_New_One()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToDelete = Child3.Key;
|
||||
Guid node = Child1.Key;
|
||||
|
||||
// Act
|
||||
await ContentEditingService.DeleteAsync(nodeToDelete, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterDeletion);
|
||||
var siblingsKeysAfterDeletionList = siblingsKeysAfterDeletion.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, siblingsKeysAfterDeletionList.Count);
|
||||
Assert.AreEqual(Child2.Key, siblingsKeysAfterDeletionList[0]);
|
||||
});
|
||||
|
||||
// Create a new sibling under the same parent
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateContentCreateModel("Child 4", key, Root.Key);
|
||||
await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterCreation);
|
||||
var siblingsKeysAfterCreationList = siblingsKeysAfterCreation.ToList();
|
||||
|
||||
// Verify sibling order after creating the new content
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, siblingsKeysAfterCreationList.Count);
|
||||
Assert.AreEqual(Child2.Key, siblingsKeysAfterCreationList[0]);
|
||||
Assert.AreEqual(key, siblingsKeysAfterCreationList[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,72 @@ public partial class DocumentNavigationServiceTests
|
||||
Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Content root
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF")] // Child 3
|
||||
[TestCase("F381906C-223C-4466-80F7-B63B4EE073F8")] // Grandchild 4
|
||||
public async Task Moving_Content_Adds_It_Last(Guid? targetParentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMove = Grandchild1.Key;
|
||||
|
||||
// Act
|
||||
await ContentEditingService.MoveAsync(nodeToMove, targetParentKey, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
if (targetParentKey is null)
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(nodeToMove, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(targetParentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(nodeToMove, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more test cases
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Siblings_Updates_When_Moving_Content_And_Adding_New_One()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMove = Child3.Key;
|
||||
Guid node = Child1.Key;
|
||||
|
||||
// Act
|
||||
await ContentEditingService.MoveAsync(nodeToMove, null, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterMoving);
|
||||
var siblingsKeysAfterMovingList = siblingsKeysAfterMoving.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, siblingsKeysAfterMovingList.Count);
|
||||
Assert.AreEqual(Child2.Key, siblingsKeysAfterMovingList[0]);
|
||||
});
|
||||
|
||||
// Create a new sibling under the same parent
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateContentCreateModel("Child 4", key, Root.Key);
|
||||
await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterCreation);
|
||||
var siblingsKeysAfterCreationList = siblingsKeysAfterCreation.ToList();
|
||||
|
||||
// Verify sibling order after creating the new content
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, siblingsKeysAfterCreationList.Count);
|
||||
Assert.AreEqual(Child2.Key, siblingsKeysAfterCreationList[0]);
|
||||
Assert.AreEqual(key, siblingsKeysAfterCreationList[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
public partial class DocumentNavigationServiceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Structure_Updates_When_Moving_Content_To_Recycle_Bin()
|
||||
public async Task Parent_And_Descendants_Are_Updated_When_Content_Is_Moved_To_Recycle_Bin()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMoveToRecycleBin = Child3.Key;
|
||||
@@ -19,7 +19,7 @@ public partial class DocumentNavigationServiceTests
|
||||
DocumentNavigationQueryService.TryGetDescendantsKeys(nodeToMoveToRecycleBin, out IEnumerable<Guid> initialDescendantsKeys);
|
||||
var beforeMoveDescendants = initialDescendantsKeys.ToList();
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(originalParentKey.Value, out IEnumerable<Guid> initialParentChildrenKeys);
|
||||
var beforeMoveParentSiblingsCount = initialParentChildrenKeys.Count();
|
||||
var beforeMoveParentChildrenCount = initialParentChildrenKeys.Count();
|
||||
|
||||
// Act
|
||||
await ContentEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey);
|
||||
@@ -30,7 +30,7 @@ public partial class DocumentNavigationServiceTests
|
||||
DocumentNavigationQueryService.TryGetDescendantsKeysInBin(nodeToMoveToRecycleBin, out IEnumerable<Guid> afterMoveDescendantsKeys);
|
||||
var afterMoveDescendants = afterMoveDescendantsKeys.ToList();
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys((Guid)originalParentKey, out IEnumerable<Guid> afterMoveParentChildrenKeys);
|
||||
var afterMoveParentSiblingsCount = afterMoveParentChildrenKeys.Count();
|
||||
var afterMoveParentChildrenCount = afterMoveParentChildrenKeys.Count();
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeysInBin(nodeInRecycleBin, out IEnumerable<Guid> afterMoveRecycleBinSiblingsKeys);
|
||||
var afterMoveRecycleBinSiblingsCount = afterMoveRecycleBinSiblingsKeys.Count();
|
||||
|
||||
@@ -42,8 +42,78 @@ public partial class DocumentNavigationServiceTests
|
||||
|
||||
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(beforeMoveParentChildrenCount - 1, afterMoveParentChildrenCount);
|
||||
Assert.AreEqual(beforeMoveRecycleBinSiblingsCount + 1, afterMoveRecycleBinSiblingsCount);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Add more test cases
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Siblings_Updates_When_Moving_Content_To_Recycle_Bin_And_Adding_New_One()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMoveToRecycleBin = Child3.Key;
|
||||
Guid node = Child1.Key;
|
||||
|
||||
// Act
|
||||
await ContentEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterDeletion);
|
||||
var siblingsKeysAfterDeletionList = siblingsKeysAfterDeletion.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, siblingsKeysAfterDeletionList.Count);
|
||||
Assert.AreEqual(Child2.Key, siblingsKeysAfterDeletionList[0]);
|
||||
});
|
||||
|
||||
// Create a new sibling under the same parent
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateContentCreateModel("Child 4", key, Root.Key);
|
||||
await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterCreation);
|
||||
var siblingsKeysAfterCreationList = siblingsKeysAfterCreation.ToList();
|
||||
|
||||
// Verify sibling order after creating the new content
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, siblingsKeysAfterCreationList.Count);
|
||||
Assert.AreEqual(Child2.Key, siblingsKeysAfterCreationList[0]);
|
||||
Assert.AreEqual(key, siblingsKeysAfterCreationList[1]);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Chilldren_Is_Maintained_When_Moving_Content_To_Recycle_Bin()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMoveToRecycleBin = Child1.Key;
|
||||
|
||||
// Create a new grandchild under Child1
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateContentCreateModel("Grandchild 3", key, nodeToMoveToRecycleBin);
|
||||
await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(nodeToMoveToRecycleBin, out IEnumerable<Guid> childrenKeysBeforeDeletion);
|
||||
var childrenKeysBeforeDeletionList = childrenKeysBeforeDeletion.ToList();
|
||||
|
||||
// Act
|
||||
await ContentEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetChildrenKeysInBin(nodeToMoveToRecycleBin, out IEnumerable<Guid> childrenKeysAfterDeletion);
|
||||
var childrenKeysAfterDeletionList = childrenKeysAfterDeletion.ToList();
|
||||
|
||||
// Verify children order in the bin
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(3, childrenKeysAfterDeletionList.Count);
|
||||
Assert.AreEqual(Grandchild1.Key, childrenKeysAfterDeletionList[0]);
|
||||
Assert.AreEqual(Grandchild2.Key, childrenKeysAfterDeletionList[1]);
|
||||
Assert.AreEqual(key, childrenKeysAfterDeletionList[2]);
|
||||
Assert.IsTrue(childrenKeysBeforeDeletionList.SequenceEqual(childrenKeysAfterDeletionList));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,37 @@ public partial class DocumentNavigationServiceTests
|
||||
Assert.AreEqual(targetParentKey, restoredItemParentKey);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Content root
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573")] // Grandchild 1
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
public async Task Restoring_Content_Adds_It_Last(Guid? targetParentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToRestore = Child3.Key;
|
||||
|
||||
// Move node to recycle bin
|
||||
await ContentEditingService.MoveToRecycleBinAsync(nodeToRestore, Constants.Security.SuperUserKey);
|
||||
|
||||
// Act
|
||||
await ContentEditingService.RestoreAsync(nodeToRestore, targetParentKey, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
if (targetParentKey is null)
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(nodeToRestore, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(targetParentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(nodeToRestore, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services;
|
||||
|
||||
public partial class DocumentNavigationServiceTests
|
||||
{
|
||||
[Test]
|
||||
public async Task Structure_Updates_When_Reversing_Children_Sort_Order()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToSortItsChildren = Root.Key;
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(nodeToSortItsChildren, out IEnumerable<Guid> initialChildrenKeys);
|
||||
List<Guid> initialChildrenKeysList = initialChildrenKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(3, initialChildrenKeysList.Count);
|
||||
|
||||
// Assert initial order
|
||||
Assert.AreEqual(Child1.Key, initialChildrenKeysList[0]);
|
||||
Assert.AreEqual(Child2.Key, initialChildrenKeysList[1]);
|
||||
Assert.AreEqual(Child3.Key, initialChildrenKeysList[2]);
|
||||
});
|
||||
|
||||
IEnumerable<SortingModel> sortingModels = initialChildrenKeys
|
||||
.Reverse()
|
||||
.Select((key, index) => new SortingModel { Key = key, SortOrder = index });
|
||||
|
||||
// Act
|
||||
await ContentEditingService.SortAsync(nodeToSortItsChildren, sortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(nodeToSortItsChildren, out IEnumerable<Guid> sortedChildrenKeys);
|
||||
List<Guid> sortedChildrenKeysList = sortedChildrenKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(Child3.Key, sortedChildrenKeysList[0]);
|
||||
Assert.AreEqual(Child2.Key, sortedChildrenKeysList[1]);
|
||||
Assert.AreEqual(Child1.Key, sortedChildrenKeysList[2]);
|
||||
});
|
||||
|
||||
var expectedChildrenKeysList = initialChildrenKeys.Reverse().ToList();
|
||||
|
||||
// Check that the order matches what is expected
|
||||
Assert.IsTrue(expectedChildrenKeysList.SequenceEqual(sortedChildrenKeysList));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Structure_Updates_When_Children_Have_Custom_Sort_Order()
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Root.Key;
|
||||
var customSortingModels = new List<SortingModel>
|
||||
{
|
||||
new() { Key = Child2.Key, SortOrder = 0 }, // Move Child 2 to the position 1
|
||||
new() { Key = Child3.Key, SortOrder = 1 }, // Move Child 3 to the position 2
|
||||
new() { Key = Child1.Key, SortOrder = 2 }, // Move Child 1 to the position 3
|
||||
};
|
||||
|
||||
// Act
|
||||
await ContentEditingService.SortAsync(node, customSortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetChildrenKeys(node, out IEnumerable<Guid> sortedChildrenKeys);
|
||||
List<Guid> sortedChildrenKeysList = sortedChildrenKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(Child2.Key, sortedChildrenKeysList[0]);
|
||||
Assert.AreEqual(Child3.Key, sortedChildrenKeysList[1]);
|
||||
Assert.AreEqual(Child1.Key, sortedChildrenKeysList[2]);
|
||||
});
|
||||
|
||||
var expectedChildrenKeysList = customSortingModels
|
||||
.OrderBy(x => x.SortOrder)
|
||||
.Select(x => x.Key)
|
||||
.ToList();
|
||||
|
||||
// Check that the order matches what is expected
|
||||
Assert.IsTrue(expectedChildrenKeysList.SequenceEqual(sortedChildrenKeysList));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Structure_Updates_When_Sorting_Items_At_Root()
|
||||
{
|
||||
// Arrange
|
||||
var anotherRootCreateModel = CreateContentCreateModel("Root 2", Guid.NewGuid(), Constants.System.RootKey);
|
||||
await ContentEditingService.CreateAsync(anotherRootCreateModel, Constants.Security.SuperUserKey);
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> initialRootKeys);
|
||||
|
||||
var sortingModels = initialRootKeys
|
||||
.Reverse()
|
||||
.Select((rootKey, index) => new SortingModel { Key = rootKey, SortOrder = index });
|
||||
|
||||
// Act
|
||||
await ContentEditingService.SortAsync(Constants.System.RootKey, sortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> sortedRootKeys);
|
||||
|
||||
var expectedRootKeysList = initialRootKeys.Reverse().ToList();
|
||||
|
||||
// Check that the order matches what is expected
|
||||
Assert.IsTrue(expectedRootKeysList.SequenceEqual(sortedRootKeys));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Descendants_Are_Returned_In_Correct_Order_After_Children_Are_Reordered()
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Root.Key;
|
||||
DocumentNavigationQueryService.TryGetDescendantsKeys(node, out IEnumerable<Guid> initialDescendantsKeys);
|
||||
|
||||
var customSortingModels = new List<SortingModel>
|
||||
{
|
||||
new() { Key = Child3.Key, SortOrder = 0 }, // Move Child 3 to the position 1
|
||||
new() { Key = Child1.Key, SortOrder = 1 }, // Move Child 1 to the position 2
|
||||
new() { Key = Child2.Key, SortOrder = 2 }, // Move Child 2 to the position 3
|
||||
};
|
||||
|
||||
var expectedDescendantsOrder = new List<Guid>
|
||||
{
|
||||
Child3.Key, Grandchild4.Key, // Child 3 and its descendants
|
||||
Child1.Key, Grandchild1.Key, Grandchild2.Key, // Child 1 and its descendants
|
||||
Child2.Key, Grandchild3.Key, GreatGrandchild1.Key, // Child 2 and its descendants
|
||||
};
|
||||
|
||||
// Act
|
||||
await ContentEditingService.SortAsync(node, customSortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetDescendantsKeys(node, out IEnumerable<Guid> updatedDescendantsKeys);
|
||||
List<Guid> updatedDescendantsKeysList = updatedDescendantsKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.IsFalse(initialDescendantsKeys.SequenceEqual(updatedDescendantsKeysList));
|
||||
Assert.IsTrue(expectedDescendantsOrder.SequenceEqual(updatedDescendantsKeysList));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(1, 2, 0, new[] { "B606E3FF-E070-4D46-8CB9-D31352029FDF", "C6173927-0C59-4778-825D-D7B9F45D8DDE" })] // Custom sort order: Child 3, Child 1, Child 2; Expected order: Child 3, Child 1
|
||||
[TestCase(0, 1, 2, new[] { "C6173927-0C59-4778-825D-D7B9F45D8DDE", "B606E3FF-E070-4D46-8CB9-D31352029FDF" })] // Custom sort order: Child 1, Child 2, Child 3; Expected order: Child 1, Child 3
|
||||
[TestCase(2, 0, 1, new[] { "B606E3FF-E070-4D46-8CB9-D31352029FDF", "C6173927-0C59-4778-825D-D7B9F45D8DDE" })] // Custom sort order: Child 2, Child 3, Child 1; Expected order: Child 3, Child 1
|
||||
public async Task Siblings_Are_Returned_In_Correct_Order_After_Sorting(int sortOrder1, int sortOrder2, int sortOrder3, string[] expectedSiblings)
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Child2.Key;
|
||||
|
||||
var customSortingModels = new List<SortingModel>
|
||||
{
|
||||
new() { Key = Child1.Key, SortOrder = sortOrder1 }, // Move Child 1 to the position sortOrder1
|
||||
new() { Key = Child2.Key, SortOrder = sortOrder2 }, // Move Child 2 to the position sortOrder2
|
||||
new() { Key = Child3.Key, SortOrder = sortOrder3 }, // Move Child 3 to the position sortOrder3
|
||||
};
|
||||
|
||||
Guid[] expectedSiblingsOrder = Array.ConvertAll(expectedSiblings, Guid.Parse);
|
||||
|
||||
// Act
|
||||
await ContentEditingService.SortAsync(Root.Key, customSortingModels, Constants.Security.SuperUserKey); // Using the parent key here
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> sortedSiblingsKeys);
|
||||
var sortedSiblingsKeysList = sortedSiblingsKeys.ToList();
|
||||
|
||||
Assert.IsTrue(expectedSiblingsOrder.SequenceEqual(sortedSiblingsKeysList));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Siblings_Are_Returned_In_Correct_Order_After_Sorting_At_Root()
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Root.Key;
|
||||
var anotherRootCreateModel1 = CreateContentCreateModel("Root 2", Guid.NewGuid(), Constants.System.RootKey);
|
||||
await ContentEditingService.CreateAsync(anotherRootCreateModel1, Constants.Security.SuperUserKey);
|
||||
var anotherRootCreateModel2 = CreateContentCreateModel("Root 3", Guid.NewGuid(), Constants.System.RootKey);
|
||||
await ContentEditingService.CreateAsync(anotherRootCreateModel2, Constants.Security.SuperUserKey);
|
||||
DocumentNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> initialRootKeys);
|
||||
|
||||
var sortingModels = initialRootKeys
|
||||
.Reverse()
|
||||
.Select((rootKey, index) => new SortingModel { Key = rootKey, SortOrder = index });
|
||||
|
||||
var expectedSiblingsKeysList = initialRootKeys.Reverse().Where(k => k != node).ToList();
|
||||
|
||||
// Act
|
||||
await ContentEditingService.SortAsync(Constants.System.RootKey, sortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
DocumentNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> sortedSiblingsKeys);
|
||||
var sortedSiblingsKeysList = sortedSiblingsKeys.ToList();
|
||||
|
||||
Assert.IsTrue(expectedSiblingsKeysList.SequenceEqual(sortedSiblingsKeysList));
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ public partial class MediaNavigationServiceTests
|
||||
// Arrange
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(Album.Key, out IEnumerable<Guid> initialSiblingsKeys);
|
||||
var initialRootNodeSiblingsCount = initialSiblingsKeys.Count();
|
||||
var createModel = CreateMediaCreateModel("Root Image", Guid.NewGuid(), ImageMediaType.Key);
|
||||
var createModel = CreateMediaCreateModel("Album 2", Guid.NewGuid(), FolderMediaType.Key);
|
||||
|
||||
// Act
|
||||
var createAttempt = await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
@@ -54,4 +54,32 @@ public partial class MediaNavigationServiceTests
|
||||
Assert.IsTrue(childrenList.Contains(createdItemKey));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Media root
|
||||
[TestCase("1CD97C02-8534-4B72-AE9E-AE52EC94CF31")] // Album
|
||||
[TestCase("139DC977-E50F-4382-9728-B278C4B7AC6A")] // Sub-album 1
|
||||
[TestCase("DBCAFF2F-BFA4-4744-A948-C290C432D564")] // Sub-album 2
|
||||
[TestCase("E0B23D56-9A0E-4FC4-BD42-834B73B4C7AB")] // Sub-sub-album 1
|
||||
public async Task Creating_Child_Media_Adds_It_As_The_Last_Child(Guid? parentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid newNodeKey = Guid.NewGuid();
|
||||
var createModel = CreateMediaCreateModel("Child Image", newNodeKey, ImageMediaType.Key, parentKey);
|
||||
|
||||
// Act
|
||||
await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
if (parentKey is null)
|
||||
{
|
||||
MediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(newNodeKey, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(parentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(newNodeKey, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,42 @@ public partial class MediaNavigationServiceTests
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Add more test cases
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Siblings_Updates_When_Deleting_Media_And_Adding_New_One()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToDelete = SubAlbum2.Key;
|
||||
Guid node = Image1.Key;
|
||||
|
||||
// Act
|
||||
await MediaEditingService.DeleteAsync(nodeToDelete, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterDeletion);
|
||||
var siblingsKeysAfterDeletionList = siblingsKeysAfterDeletion.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, siblingsKeysAfterDeletionList.Count);
|
||||
Assert.AreEqual(SubAlbum1.Key, siblingsKeysAfterDeletionList[0]);
|
||||
});
|
||||
|
||||
// Create a new sibling under the same parent
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateMediaCreateModel("Child Image", key, ImageMediaType.Key, Album.Key);
|
||||
await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterCreation);
|
||||
var siblingsKeysAfterCreationList = siblingsKeysAfterCreation.ToList();
|
||||
|
||||
// Verify sibling order after creating the new media
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, siblingsKeysAfterCreationList.Count);
|
||||
Assert.AreEqual(SubAlbum1.Key, siblingsKeysAfterCreationList[0]);
|
||||
Assert.AreEqual(key, siblingsKeysAfterCreationList[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,68 @@ public partial class MediaNavigationServiceTests
|
||||
Assert.AreEqual(beforeMoveDescendants, afterMoveDescendants);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Media root
|
||||
[TestCase("1CD97C02-8534-4B72-AE9E-AE52EC94CF31")] // Album
|
||||
[TestCase("DBCAFF2F-BFA4-4744-A948-C290C432D564")] // Sub-album 2
|
||||
[TestCase("E0B23D56-9A0E-4FC4-BD42-834B73B4C7AB")] // Sub-sub-album 1
|
||||
public async Task Moving_Media_Adds_It_Last(Guid? targetParentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMove = Image2.Key;
|
||||
|
||||
// Act
|
||||
await MediaEditingService.MoveAsync(nodeToMove, targetParentKey, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
if (targetParentKey is null)
|
||||
{
|
||||
MediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(nodeToMove, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(targetParentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(nodeToMove, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more test cases
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Siblings_Updates_When_Moving_Media_And_Adding_New_One()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMove = SubAlbum2.Key;
|
||||
Guid node = Image1.Key;
|
||||
|
||||
// Act
|
||||
await MediaEditingService.MoveAsync(nodeToMove, null, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterMoving);
|
||||
var siblingsKeysAfterMovingList = siblingsKeysAfterMoving.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, siblingsKeysAfterMovingList.Count);
|
||||
Assert.AreEqual(SubAlbum1.Key, siblingsKeysAfterMovingList[0]);
|
||||
});
|
||||
|
||||
// Create a new sibling under the same parent
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateMediaCreateModel("Child Image", key, ImageMediaType.Key, Album.Key);
|
||||
await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterCreation);
|
||||
var siblingsKeysAfterCreationList = siblingsKeysAfterCreation.ToList();
|
||||
|
||||
// Verify sibling order after creating the new media
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, siblingsKeysAfterCreationList.Count);
|
||||
Assert.AreEqual(SubAlbum1.Key, siblingsKeysAfterMovingList[0]);
|
||||
Assert.AreEqual(key, siblingsKeysAfterCreationList[1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,74 @@ public partial class MediaNavigationServiceTests
|
||||
Assert.AreEqual(beforeMoveRecycleBinSiblingsCount + 1, afterMoveRecycleBinSiblingsCount);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Add more test cases
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Siblings_Updates_When_Moving_Media_To_Recycle_Bin_And_Adding_New_One()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMoveToRecycleBin = SubAlbum2.Key;
|
||||
Guid node = Image1.Key;
|
||||
|
||||
// Act
|
||||
await MediaEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterDeletion);
|
||||
var siblingsKeysAfterDeletionList = siblingsKeysAfterDeletion.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(1, siblingsKeysAfterDeletionList.Count);
|
||||
Assert.AreEqual(SubAlbum1.Key, siblingsKeysAfterDeletionList[0]);
|
||||
});
|
||||
|
||||
// Create a new sibling under the same parent
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateMediaCreateModel("Child Image", key, ImageMediaType.Key, Album.Key);
|
||||
await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> siblingsKeysAfterCreation);
|
||||
var siblingsKeysAfterCreationList = siblingsKeysAfterCreation.ToList();
|
||||
|
||||
// Verify sibling order after creating the new media
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(2, siblingsKeysAfterCreationList.Count);
|
||||
Assert.AreEqual(SubAlbum1.Key, siblingsKeysAfterDeletionList[0]);
|
||||
Assert.AreEqual(key, siblingsKeysAfterCreationList[1]);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Sort_Order_Of_Chilldren_Is_Maintained_When_Moving_Media_To_Recycle_Bin()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMoveToRecycleBin = SubAlbum1.Key;
|
||||
|
||||
// Create a new grandchild under Child1
|
||||
var key = Guid.NewGuid();
|
||||
var createModel = CreateMediaCreateModel("Image 5", key, ImageMediaType.Key, nodeToMoveToRecycleBin);
|
||||
await MediaEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
|
||||
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(nodeToMoveToRecycleBin, out IEnumerable<Guid> childrenKeysBeforeDeletion);
|
||||
var childrenKeysBeforeDeletionList = childrenKeysBeforeDeletion.ToList();
|
||||
|
||||
// Act
|
||||
await MediaEditingService.MoveToRecycleBinAsync(nodeToMoveToRecycleBin, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetChildrenKeysInBin(nodeToMoveToRecycleBin, out IEnumerable<Guid> childrenKeysAfterDeletion);
|
||||
var childrenKeysAfterDeletionList = childrenKeysAfterDeletion.ToList();
|
||||
|
||||
// Verify children order in the bin
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(3, childrenKeysAfterDeletionList.Count);
|
||||
Assert.AreEqual(Image2.Key, childrenKeysAfterDeletionList[0]);
|
||||
Assert.AreEqual(Image3.Key, childrenKeysAfterDeletionList[1]);
|
||||
Assert.AreEqual(key, childrenKeysAfterDeletionList[2]);
|
||||
Assert.IsTrue(childrenKeysBeforeDeletionList.SequenceEqual(childrenKeysAfterDeletionList));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +50,33 @@ public partial class MediaNavigationServiceTests
|
||||
Assert.AreEqual(targetParentKey, restoredItemParentKey);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(null)] // Media root
|
||||
[TestCase("139DC977-E50F-4382-9728-B278C4B7AC6A")] // Sub-album 1
|
||||
[TestCase("DBCAFF2F-BFA4-4744-A948-C290C432D564")] // Sub-album 2
|
||||
[TestCase("E0B23D56-9A0E-4FC4-BD42-834B73B4C7AB")] // Sub-sub-album 1
|
||||
public async Task Restoring_Content_Adds_It_Last(Guid? targetParentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToRestore = Image1.Key;
|
||||
|
||||
// Move node to recycle bin
|
||||
await MediaEditingService.MoveToRecycleBinAsync(nodeToRestore, Constants.Security.SuperUserKey);
|
||||
|
||||
// Act
|
||||
await MediaEditingService.RestoreAsync(nodeToRestore, targetParentKey, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
if (targetParentKey is null)
|
||||
{
|
||||
MediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
|
||||
Assert.AreEqual(nodeToRestore, rootKeys.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(targetParentKey.Value, out IEnumerable<Guid> childrenKeys);
|
||||
Assert.AreEqual(nodeToRestore, childrenKeys.Last());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
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_Updates_When_Reversing_Children_Sort_Order()
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToSortItsChildren = Album.Key;
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(nodeToSortItsChildren, out IEnumerable<Guid> initialChildrenKeys);
|
||||
List<Guid> initialChildrenKeysList = initialChildrenKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(3, initialChildrenKeysList.Count);
|
||||
|
||||
// Assert initial order
|
||||
Assert.AreEqual(Image1.Key, initialChildrenKeysList[0]);
|
||||
Assert.AreEqual(SubAlbum1.Key, initialChildrenKeysList[1]);
|
||||
Assert.AreEqual(SubAlbum2.Key, initialChildrenKeysList[2]);
|
||||
});
|
||||
|
||||
IEnumerable<SortingModel> sortingModels = initialChildrenKeys
|
||||
.Reverse()
|
||||
.Select((key, index) => new SortingModel { Key = key, SortOrder = index });
|
||||
|
||||
// Act
|
||||
await MediaEditingService.SortAsync(nodeToSortItsChildren, sortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(nodeToSortItsChildren, out IEnumerable<Guid> sortedChildrenKeys);
|
||||
List<Guid> sortedChildrenKeysList = sortedChildrenKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(SubAlbum2.Key, sortedChildrenKeysList[0]);
|
||||
Assert.AreEqual(SubAlbum1.Key, sortedChildrenKeysList[1]);
|
||||
Assert.AreEqual(Image1.Key, sortedChildrenKeysList[2]);
|
||||
});
|
||||
|
||||
var expectedChildrenKeysList = initialChildrenKeys.Reverse().ToList();
|
||||
|
||||
// Check that the order matches what is expected
|
||||
Assert.IsTrue(expectedChildrenKeysList.SequenceEqual(sortedChildrenKeysList));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Structure_Updates_When_Children_Have_Custom_Sort_Order()
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Album.Key;
|
||||
var customSortingModels = new List<SortingModel>
|
||||
{
|
||||
new() { Key = SubAlbum1.Key, SortOrder = 0 }, // Move Sub-album 1 to the position 1
|
||||
new() { Key = SubAlbum2.Key, SortOrder = 1 }, // Move Sub-album 2 to the position 2
|
||||
new() { Key = Image1.Key, SortOrder = 2 }, // Move Image 1 to the position 3
|
||||
};
|
||||
|
||||
// Act
|
||||
await MediaEditingService.SortAsync(node, customSortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetChildrenKeys(node, out IEnumerable<Guid> sortedChildrenKeys);
|
||||
List<Guid> sortedChildrenKeysList = sortedChildrenKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.AreEqual(SubAlbum1.Key, sortedChildrenKeysList[0]);
|
||||
Assert.AreEqual(SubAlbum2.Key, sortedChildrenKeysList[1]);
|
||||
Assert.AreEqual(Image1.Key, sortedChildrenKeysList[2]);
|
||||
});
|
||||
|
||||
var expectedChildrenKeysList = customSortingModels
|
||||
.OrderBy(x => x.SortOrder)
|
||||
.Select(x => x.Key)
|
||||
.ToList();
|
||||
|
||||
// Check that the order matches what is expected
|
||||
Assert.IsTrue(expectedChildrenKeysList.SequenceEqual(sortedChildrenKeysList));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Structure_Updates_When_Sorting_Items_At_Root()
|
||||
{
|
||||
// Arrange
|
||||
var anotherRootCreateModel = CreateMediaCreateModel("Album 2", Guid.NewGuid(), FolderMediaType.Key, Constants.System.RootKey);
|
||||
await MediaEditingService.CreateAsync(anotherRootCreateModel, Constants.Security.SuperUserKey);
|
||||
MediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> initialRootKeys);
|
||||
|
||||
var sortingModels = initialRootKeys
|
||||
.Reverse()
|
||||
.Select((rootKey, index) => new SortingModel { Key = rootKey, SortOrder = index });
|
||||
|
||||
// Act
|
||||
await MediaEditingService.SortAsync(Constants.System.RootKey, sortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> sortedRootKeys);
|
||||
|
||||
var expectedRootKeysList = initialRootKeys.Reverse().ToList();
|
||||
|
||||
// Check that the order matches what is expected
|
||||
Assert.IsTrue(expectedRootKeysList.SequenceEqual(sortedRootKeys));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Descendants_Are_Returned_In_Correct_Order_After_Children_Are_Reordered()
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Album.Key;
|
||||
MediaNavigationQueryService.TryGetDescendantsKeys(node, out IEnumerable<Guid> initialDescendantsKeys);
|
||||
|
||||
var customSortingModels = new List<SortingModel>
|
||||
{
|
||||
new() { Key = SubAlbum2.Key, SortOrder = 0 }, // Move Sub-album 2 to the position 1
|
||||
new() { Key = Image1.Key, SortOrder = 1 }, // Move Image 1 to the position 2
|
||||
new() { Key = SubAlbum1.Key, SortOrder = 2 }, // Move Sub-album 1 to the position 3
|
||||
};
|
||||
|
||||
var expectedDescendantsOrder = new List<Guid>
|
||||
{
|
||||
SubAlbum2.Key, SubSubAlbum1.Key, Image4.Key, // Sub-album 2 and its descendants
|
||||
Image1.Key, // Image 1
|
||||
SubAlbum1.Key, Image2.Key, Image3.Key, // Sub-album 1 and its descendants
|
||||
};
|
||||
|
||||
// Act
|
||||
await MediaEditingService.SortAsync(node, customSortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetDescendantsKeys(node, out IEnumerable<Guid> updatedDescendantsKeys);
|
||||
List<Guid> updatedDescendantsKeysList = updatedDescendantsKeys.ToList();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.IsFalse(initialDescendantsKeys.SequenceEqual(updatedDescendantsKeysList));
|
||||
Assert.IsTrue(expectedDescendantsOrder.SequenceEqual(updatedDescendantsKeysList));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(1, 2, 0, new[] { "DBCAFF2F-BFA4-4744-A948-C290C432D564", "03976EBE-A942-4F24-9885-9186E99AEF7C" })] // Custom sort order: Sub-album 2, Image 1, Sub-album 1; Expected order: Sub-album 2, Image 1
|
||||
[TestCase(0, 1, 2, new[] { "03976EBE-A942-4F24-9885-9186E99AEF7C", "DBCAFF2F-BFA4-4744-A948-C290C432D564" })] // Custom sort order: Image 1, Sub-album 1, Sub-album 2; Expected order: Image 1, Sub-album 2
|
||||
[TestCase(2, 0, 1, new[] { "DBCAFF2F-BFA4-4744-A948-C290C432D564", "03976EBE-A942-4F24-9885-9186E99AEF7C" })] // Custom sort order: Sub-album 1, Sub-album 2, Image 1; Expected order: Sub-album 2, Image 1
|
||||
public async Task Siblings_Are_Returned_In_Correct_Order_After_Sorting(int sortOrder1, int sortOrder2, int sortOrder3, string[] expectedSiblings)
|
||||
{
|
||||
// Arrange
|
||||
Guid node = SubAlbum1.Key;
|
||||
|
||||
var customSortingModels = new List<SortingModel>
|
||||
{
|
||||
new() { Key = Image1.Key, SortOrder = sortOrder1 }, // Move Image 1 to the position sortOrder1
|
||||
new() { Key = SubAlbum1.Key, SortOrder = sortOrder2 }, // Move Sub-album 1 to the position sortOrder2
|
||||
new() { Key = SubAlbum2.Key, SortOrder = sortOrder3 }, // Move Sub-album 2 to the position sortOrder3
|
||||
};
|
||||
|
||||
Guid[] expectedSiblingsOrder = Array.ConvertAll(expectedSiblings, Guid.Parse);
|
||||
|
||||
// Act
|
||||
await MediaEditingService.SortAsync(Album.Key, customSortingModels, Constants.Security.SuperUserKey); // Using the parent key here
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> sortedSiblingsKeys);
|
||||
var sortedSiblingsKeysList = sortedSiblingsKeys.ToList();
|
||||
|
||||
Assert.IsTrue(expectedSiblingsOrder.SequenceEqual(sortedSiblingsKeysList));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Siblings_Are_Returned_In_Correct_Order_After_Sorting_At_Root()
|
||||
{
|
||||
// Arrange
|
||||
Guid node = Album.Key;
|
||||
var anotherRootCreateModel1 = CreateMediaCreateModel("Album 2", Guid.NewGuid(), FolderMediaType.Key, Constants.System.RootKey);
|
||||
await MediaEditingService.CreateAsync(anotherRootCreateModel1, Constants.Security.SuperUserKey);
|
||||
var anotherRootCreateModel2 = CreateMediaCreateModel("Album 3", Guid.NewGuid(), FolderMediaType.Key, Constants.System.RootKey);
|
||||
await MediaEditingService.CreateAsync(anotherRootCreateModel2, Constants.Security.SuperUserKey);
|
||||
MediaNavigationQueryService.TryGetRootKeys(out IEnumerable<Guid> initialRootKeys);
|
||||
|
||||
var sortingModels = initialRootKeys
|
||||
.Reverse()
|
||||
.Select((rootKey, index) => new SortingModel { Key = rootKey, SortOrder = index });
|
||||
|
||||
var expectedSiblingsKeysList = initialRootKeys.Reverse().Where(k => k != node).ToList();
|
||||
|
||||
// Act
|
||||
await MediaEditingService.SortAsync(Constants.System.RootKey, sortingModels, Constants.Security.SuperUserKey);
|
||||
|
||||
// Assert
|
||||
MediaNavigationQueryService.TryGetSiblingsKeys(node, out IEnumerable<Guid> sortedSiblingsKeys);
|
||||
var sortedSiblingsKeysList = sortedSiblingsKeys.ToList();
|
||||
|
||||
Assert.IsTrue(expectedSiblingsKeysList.SequenceEqual(sortedSiblingsKeysList));
|
||||
}
|
||||
}
|
||||
@@ -188,6 +188,9 @@
|
||||
<Compile Update="Umbraco.Core\Services\DocumentNavigationServiceTests.Restore.cs">
|
||||
<DependentUpon>DocumentNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\DocumentNavigationServiceTests.Sort.cs">
|
||||
<DependentUpon>DocumentNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\DocumentNavigationServiceTests.Update.cs">
|
||||
<DependentUpon>DocumentNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
@@ -209,9 +212,15 @@
|
||||
<Compile Update="Umbraco.Core\Services\MediaNavigationServiceTests.Rebuild.cs">
|
||||
<DependentUpon>MediaNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\DocumentNavigationServiceTests.Sort.cs">
|
||||
<DependentUpon>DocumentNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\MediaNavigationServiceTests.Restore.cs">
|
||||
<DependentUpon>MediaNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\MediaNavigationServiceTests.Sort.cs">
|
||||
<DependentUpon>MediaNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Umbraco.Core\Services\MediaNavigationServiceTests.Update.cs">
|
||||
<DependentUpon>MediaNavigationServiceTests.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
@@ -177,7 +177,7 @@ public class ContentNavigationServiceBaseTests
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732", new[] { "56E29EA9-E224-4210-A59F-7C2C5C0C5CC7" })] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7", new string[0])] // Great-grandchild 1
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", new[] { "F381906C-223C-4466-80F7-B63B4EE073F8" })] // Child 3
|
||||
public void Can_Get_Children_From_Existing_Content_Key_In_Correct_Order(Guid parentKey, string[] children)
|
||||
public void Can_Get_Children_From_Existing_Content_Key_In_Their_Order_Of_Creation(Guid parentKey, string[] children)
|
||||
{
|
||||
// Arrange
|
||||
Guid[] expectedChildren = Array.ConvertAll(children, Guid.Parse);
|
||||
@@ -251,7 +251,7 @@ public class ContentNavigationServiceBaseTests
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732", new[] { "56E29EA9-E224-4210-A59F-7C2C5C0C5CC7" })] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7", new string[0])] // Great-grandchild 1
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", new[] { "F381906C-223C-4466-80F7-B63B4EE073F8" })] // Child 3
|
||||
public void Can_Get_Descendants_From_Existing_Content_Key_In_Correct_Order(Guid parentKey, string[] descendants)
|
||||
public void Can_Get_Descendants_From_Existing_Content_Key_In_Their_Order_Of_Creation(Guid parentKey, string[] descendants)
|
||||
{
|
||||
// Arrange
|
||||
Guid[] expectedDescendants = Array.ConvertAll(descendants, Guid.Parse);
|
||||
@@ -318,7 +318,7 @@ public class ContentNavigationServiceBaseTests
|
||||
"D63C1621-C74A-4106-8587-817DEE5FB732", "60E0E5C4-084E-4144-A560-7393BEAD2E96",
|
||||
"E48DD82A-7059-418E-9B82-CDD5205796CF"
|
||||
})] // Great-grandchild 1
|
||||
public void Can_Get_Ancestors_From_Existing_Content_Key_In_Correct_Order(Guid childKey, string[] ancestors)
|
||||
public void Can_Get_Ancestors_From_Existing_Content_Key_In_Their_Order_Of_Creation(Guid childKey, string[] ancestors)
|
||||
{
|
||||
// Arrange
|
||||
Guid[] expectedAncestors = Array.ConvertAll(ancestors, Guid.Parse);
|
||||
@@ -417,7 +417,7 @@ public class ContentNavigationServiceBaseTests
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF", new string[0])] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE", new[] { "60E0E5C4-084E-4144-A560-7393BEAD2E96", "B606E3FF-E070-4D46-8CB9-D31352029FDF" })] // Child 1 - Child 2, Child 3
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", new[] { "A1B1B217-B02F-4307-862C-A5E22DB729EB" })] // Grandchild 1 - Grandchild 2
|
||||
public void Can_Get_Siblings_Of_Existing_Content_Key_In_Correct_Order(Guid childKey, string[] siblings)
|
||||
public void Can_Get_Siblings_Of_Existing_Content_Key_In_Their_Order_Of_Creation(Guid childKey, string[] siblings)
|
||||
{
|
||||
// Arrange
|
||||
Guid[] expectedSiblings = Array.ConvertAll(siblings, Guid.Parse);
|
||||
@@ -527,6 +527,30 @@ public class ContentNavigationServiceBaseTests
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
public void Moving_Node_To_Bin_Adds_It_To_Recycle_Bin_Root_As_The_Last_Item(Guid keyOfNodeToRemove)
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeInRecycleBin1 = Grandchild1;
|
||||
Guid nodeInRecycleBin2 = Child3;
|
||||
_navigationService.MoveToBin(nodeInRecycleBin1);
|
||||
_navigationService.MoveToBin(nodeInRecycleBin2);
|
||||
|
||||
// Act
|
||||
_navigationService.MoveToBin(keyOfNodeToRemove);
|
||||
|
||||
// Assert
|
||||
_navigationService.TryGetSiblingsKeysInBin(nodeInRecycleBin1, out IEnumerable<Guid> siblingsInBin);
|
||||
|
||||
Assert.AreEqual(siblingsInBin.Last(), keyOfNodeToRemove);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
@@ -629,6 +653,30 @@ public class ContentNavigationServiceBaseTests
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573")] // Grandchild 1
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF")] // Child 3
|
||||
[TestCase("F381906C-223C-4466-80F7-B63B4EE073F8")] // Grandchild 4
|
||||
public void Adding_Node_To_Parent_Adds_It_As_The_Last_Child(Guid parentKey)
|
||||
{
|
||||
// Arrange
|
||||
var newNodeKey = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
_navigationService.Add(newNodeKey, parentKey);
|
||||
|
||||
// Assert
|
||||
_navigationService.TryGetChildrenKeys(parentKey, out IEnumerable<Guid> childrenKeys);
|
||||
|
||||
Assert.AreEqual(newNodeKey, childrenKeys.Last());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cannot_Move_Node_When_Target_Parent_Does_Not_Exist()
|
||||
{
|
||||
@@ -792,10 +840,32 @@ public class ContentNavigationServiceBaseTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", "60E0E5C4-084E-4144-A560-7393BEAD2E96", 0)] // Grandchild 1 to Child 2
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", null, 1)] // Child 3 to content root
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96", "C6173927-0C59-4778-825D-D7B9F45D8DDE", 2)] // Child 2 to Child 1
|
||||
public void Moved_Node_Has_The_Same_Amount_Of_Descendants(Guid nodeToMove, Guid? targetParentKey, int initialDescendantsCount)
|
||||
[TestCase("E48DD82A-7059-418E-9B82-CDD5205796CF")] // Root
|
||||
[TestCase("C6173927-0C59-4778-825D-D7B9F45D8DDE")] // Child 1
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573")] // Grandchild 1
|
||||
[TestCase("A1B1B217-B02F-4307-862C-A5E22DB729EB")] // Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96")] // Child 2
|
||||
[TestCase("D63C1621-C74A-4106-8587-817DEE5FB732")] // Grandchild 3
|
||||
[TestCase("56E29EA9-E224-4210-A59F-7C2C5C0C5CC7")] // Great-grandchild 1
|
||||
public void Moving_Node_To_Parent_Adds_It_As_The_Last_Child(Guid targetParentKey)
|
||||
{
|
||||
// Arrange
|
||||
Guid nodeToMove = Grandchild4;
|
||||
|
||||
// Act
|
||||
_navigationService.Move(nodeToMove, targetParentKey);
|
||||
|
||||
// Assert
|
||||
_navigationService.TryGetChildrenKeys(targetParentKey, out IEnumerable<Guid> childrenKeys);
|
||||
|
||||
Assert.AreEqual(nodeToMove, childrenKeys.Last());
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", 1, "60E0E5C4-084E-4144-A560-7393BEAD2E96", 0)] // Grandchild 1 to Child 2
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", 1, null, 1)] // Child 3 to content root
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96", 2, "C6173927-0C59-4778-825D-D7B9F45D8DDE", 2)] // Child 2 to Child 1
|
||||
public void Moved_Node_Has_The_Same_Amount_Of_Descendants(Guid nodeToMove, int sortOrder, Guid? targetParentKey, int initialDescendantsCount)
|
||||
{
|
||||
// Act
|
||||
var result = _navigationService.Move(nodeToMove, targetParentKey);
|
||||
@@ -811,10 +881,10 @@ public class ContentNavigationServiceBaseTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", "A1B1B217-B02F-4307-862C-A5E22DB729EB", 0)] // Child 3 to Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96", "B606E3FF-E070-4D46-8CB9-D31352029FDF", 1)] // Child 2 to Child 3
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", "60E0E5C4-084E-4144-A560-7393BEAD2E96", 2)] // Grandchild 1 to Child 2
|
||||
public void Number_Of_Target_Parent_Descendants_Updates_When_Moving_Node_With_Descendants(Guid nodeToMove, Guid targetParentKey, int initialDescendantsCountOfTargetParent)
|
||||
[TestCase("B606E3FF-E070-4D46-8CB9-D31352029FDF", 0, "A1B1B217-B02F-4307-862C-A5E22DB729EB", 0)] // Child 3 to Grandchild 2
|
||||
[TestCase("60E0E5C4-084E-4144-A560-7393BEAD2E96", 1, "B606E3FF-E070-4D46-8CB9-D31352029FDF", 1)] // Child 2 to Child 3
|
||||
[TestCase("E856AC03-C23E-4F63-9AA9-681B42A58573", 1, "60E0E5C4-084E-4144-A560-7393BEAD2E96", 2)] // Grandchild 1 to Child 2
|
||||
public void Number_Of_Target_Parent_Descendants_Updates_When_Moving_Node_With_Descendants(Guid nodeToMove, int sortOrder, Guid targetParentKey, int initialDescendantsCountOfTargetParent)
|
||||
{
|
||||
// Arrange
|
||||
// Get the number of descendants of the node to move
|
||||
|
||||
Reference in New Issue
Block a user