From 949f35defda75b9496cd18dfe4142960ddf5d188 Mon Sep 17 00:00:00 2001
From: Nils Andresen <nils@nils-andresen.de>
Date: Thu, 14 Oct 2021 08:07:31 +0200
Subject: [PATCH] (#502) Added GetParent and GetParents to MultiSelectionPrompt

So it it possible to find the parent(s) of a given item.
---
 .../Prompts/MultiSelectionPrompt.cs           | 35 ++++++++++
 .../Unit/Prompts/MultiSelectionPromptTests.cs | 70 +++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs b/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
index 69a504e..8b92b16 100644
--- a/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
+++ b/src/Spectre.Console/Prompts/MultiSelectionPrompt.cs
@@ -112,6 +112,41 @@ namespace Spectre.Console
                 .ToList();
         }
 
+        /// <summary>
+        /// Returns all parent items of the given <paramref name="item"/>.
+        /// </summary>
+        /// <param name="item">The item for which to find the parents.</param>
+        /// <returns>The parent items, or an empty list, if the given item has no parents.</returns>
+        public IEnumerable<T> GetParents(T item)
+        {
+            var promptItem = Tree.Find(item);
+            if (promptItem == null)
+            {
+                throw new ArgumentOutOfRangeException(nameof(item), "item not found in tree.");
+            }
+
+            var parents = new List<ListPromptItem<T>>();
+            while (promptItem.Parent != null)
+            {
+                promptItem = promptItem.Parent;
+                parents.Add(promptItem);
+            }
+
+            return parents
+                .ReverseEnumerable()
+                .Select(x => x.Data);
+        }
+
+        /// <summary>
+        /// Returns the parent item of the given <paramref name="item"/>.
+        /// </summary>
+        /// <param name="item">The item for which to find the parent.</param>
+        /// <returns>The parent item, or <c>null</c> if the given item has no parent.</returns>
+        public T? GetParent(T item)
+        {
+            return GetParents(item).LastOrDefault();
+        }
+
         /// <inheritdoc/>
         ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, ListPromptState<T> state)
         {
diff --git a/test/Spectre.Console.Tests/Unit/Prompts/MultiSelectionPromptTests.cs b/test/Spectre.Console.Tests/Unit/Prompts/MultiSelectionPromptTests.cs
index b9bdcb5..a0ac81a 100644
--- a/test/Spectre.Console.Tests/Unit/Prompts/MultiSelectionPromptTests.cs
+++ b/test/Spectre.Console.Tests/Unit/Prompts/MultiSelectionPromptTests.cs
@@ -82,5 +82,75 @@ namespace Spectre.Console.Tests.Unit
             // Then
             choice.IsSelected.ShouldBeTrue();
         }
+        
+        [Fact]
+        public void Should_Get_The_Direct_Parent()
+        {
+            // Given
+            var prompt = new MultiSelectionPrompt<string>();
+            prompt.AddChoice("root").AddChild("level-1").AddChild("level-2").AddChild("item");
+            
+            // When
+            var actual = prompt.GetParent("item");
+
+            // Then
+            actual.ShouldBe("level-2");
+        }
+        
+        [Fact]
+        public void Should_Get_The_List_Of_All_Parents()
+        {
+            // Given
+            var prompt = new MultiSelectionPrompt<string>();
+            prompt.AddChoice("root").AddChild("level-1").AddChild("level-2").AddChild("item");
+            
+            // When
+            var actual = prompt.GetParents("item");
+
+            // Then
+            actual.ShouldBe(new []{"root", "level-1", "level-2"});
+        }
+        
+        [Fact]
+        public void Should_Get_An_Empty_List_Of_Parents_For_Root_Node()
+        {
+            // Given
+            var prompt = new MultiSelectionPrompt<string>();
+            prompt.AddChoice("root");
+            
+            // When
+            var actual = prompt.GetParents("root");
+
+            // Then
+            actual.ShouldBeEmpty();
+        }
+        
+        [Fact]
+        public void Should_Get_Null_As_Direct_Parent_Of_Root_Node()
+        {
+            // Given
+            var prompt = new MultiSelectionPrompt<string>();
+            prompt.AddChoice("root");
+            
+            // When
+            var actual = prompt.GetParent("root");
+
+            // Then
+            actual.ShouldBeNull();
+        }
+        
+        [Fact]
+        public void Should_Throw_When_Getting_Parents_Of_Non_Existing_Node()
+        {
+            // Given
+            var prompt = new MultiSelectionPrompt<string>();
+            prompt.AddChoice("root").AddChild("level-1").AddChild("level-2").AddChild("item");
+            
+            // When
+            Action action = () => prompt.GetParents("non-existing");
+
+            // Then
+            action.ShouldThrow<ArgumentOutOfRangeException>();
+        }
     }
 }