diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs new file mode 100644 index 00000000000..8be7dacec73 --- /dev/null +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -0,0 +1,299 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Toolkit.Collections +{ + /// + /// The extensions methods to simplify the usage of . + /// + public static class ObservableGroupedCollectionExtensions + { + /// + /// Return the first group with key. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group to query. + /// The first group matching . + /// The target group does not exist. + public static ObservableGroup First(this ObservableGroupedCollection source, TKey key) + => source.First(group => GroupKeyPredicate(group, key)); + + /// + /// Return the first group with key or null if not found. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group to query. + /// The first group matching or null. + public static ObservableGroup FirstOrDefault(this ObservableGroupedCollection source, TKey key) + => source.FirstOrDefault(group => GroupKeyPredicate(group, key)); + + /// + /// Return the element at position from the first group with key. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group to query. + /// The index of the item from the targeted group. + /// The element. + /// The target group does not exist. + /// is less than zero or is greater than the group elements' count. + public static TValue ElementAt( + this ObservableGroupedCollection source, + TKey key, + int index) + => source.First(key)[index]; + + /// + /// Return the element at position from the first group with key. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group to query. + /// The index of the item from the targeted group. + /// The element or default(TValue) if it does not exist. + public static TValue ElementAtOrDefault( + this ObservableGroupedCollection source, + TKey key, + int index) + { + var existingGroup = source.FirstOrDefault(key); + if (existingGroup is null) + { + return default; + } + + return existingGroup.ElementAtOrDefault(index); + } + + /// + /// Adds a key-value item into a target . + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where will be added. + /// The value to add. + /// The added . + public static ObservableGroup AddGroup( + this ObservableGroupedCollection source, + TKey key, + TValue value) + => AddGroup(source, key, new[] { value }); + + /// + /// Adds a key-collection item into a target . + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where will be added. + /// The collection to add. + /// The added . + public static ObservableGroup AddGroup( + this ObservableGroupedCollection source, + TKey key, + params TValue[] collection) + => source.AddGroup(key, (IEnumerable)collection); + + /// + /// Adds a key-collection item into a target . + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where will be added. + /// The collection to add. + /// The added . + public static ObservableGroup AddGroup( + this ObservableGroupedCollection source, + TKey key, + IEnumerable collection) + { + var group = new ObservableGroup(key, collection); + source.Add(group); + + return group; + } + + /// + /// Add into the first group with key. + /// If the group does not exist, it will be added. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where the should be added. + /// The item to add. + /// The instance of the which will receive the value. It will either be an existing group or a new group. + public static ObservableGroup AddItem( + this ObservableGroupedCollection source, + TKey key, + TValue item) + { + var existingGroup = source.FirstOrDefault(key); + if (existingGroup is null) + { + existingGroup = new ObservableGroup(key); + source.Add(existingGroup); + } + + existingGroup.Add(item); + return existingGroup; + } + + /// + /// Insert into the first group with key at . + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where to insert . + /// The index where to insert . + /// The item to add. + /// The instance of the which will receive the value. + /// The target group does not exist. + /// is less than zero or is greater than the group elements' count. + public static ObservableGroup InsertItem( + this ObservableGroupedCollection source, + TKey key, + int index, + TValue item) + { + var existingGroup = source.First(key); + existingGroup.Insert(index, item); + return existingGroup; + } + + /// + /// Replace the element at with in the first group with key. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where to replace the item. + /// The index where to insert . + /// The item to add. + /// The instance of the which will receive the value. + /// The target group does not exist. + /// is less than zero or is greater than the group elements' count. + public static ObservableGroup SetItem( + this ObservableGroupedCollection source, + TKey key, + int index, + TValue item) + { + var existingGroup = source.First(key); + existingGroup[index] = item; + return existingGroup; + } + + /// + /// Remove the first occurrence of the group with from the grouped collection. + /// It will not do anything if the group does not exist. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group to remove. + public static void RemoveGroup( + this ObservableGroupedCollection source, + TKey key) + { + var index = 0; + foreach (var group in source) + { + if (GroupKeyPredicate(group, key)) + { + source.RemoveAt(index); + return; + } + + index++; + } + } + + /// + /// Remove the first from the first group with from the grouped collection. + /// It will not do anything if the group or the item does not exist. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where the should be removed. + /// The item to remove. + /// If true (default value), the group will be removed once it becomes empty. + public static void RemoveItem( + this ObservableGroupedCollection source, + TKey key, + TValue item, + bool removeGroupIfEmpty = true) + { + var index = 0; + foreach (var group in source) + { + if (GroupKeyPredicate(group, key)) + { + group.Remove(item); + + if (removeGroupIfEmpty && group.Count == 0) + { + source.RemoveAt(index); + } + + return; + } + + index++; + } + } + + /// + /// Remove the item at from the first group with from the grouped collection. + /// It will not do anything if the group or the item does not exist. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key of the group where the item at should be removed. + /// The index of the item to remove in the group. + /// If true (default value), the group will be removed once it becomes empty. + public static void RemoveItemAt( + this ObservableGroupedCollection source, + TKey key, + int index, + bool removeGroupIfEmpty = true) + { + var groupIndex = 0; + foreach (var group in source) + { + if (GroupKeyPredicate(group, key)) + { + group.RemoveAt(index); + + if (removeGroupIfEmpty && group.Count == 0) + { + source.RemoveAt(groupIndex); + } + + return; + } + + groupIndex++; + } + } + + private static bool GroupKeyPredicate(ObservableGroup group, TKey expectedKey) + => EqualityComparer.Default.Equals(group.Key, expectedKey); + } +} diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs new file mode 100644 index 00000000000..0c1128be410 --- /dev/null +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -0,0 +1,539 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using FluentAssertions; +using Microsoft.Toolkit.Collections; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Linq; + +namespace UnitTests.Collections +{ + [TestClass] + public class ObservableGroupedCollectionExtensionsTests + { + [TestCategory("Collections")] + [TestMethod] + public void First_WhenGroupExists_ShouldReturnFirstGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + var target = groupedCollection.AddGroup("B", 10); + groupedCollection.AddGroup("B", 42); + + var result = groupedCollection.First("B"); + + result.Should().BeSameAs(target); + } + + [TestCategory("Collections")] + [TestMethod] + public void First_WhenGroupDoesNotExist_ShouldThrow() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + + Action action = () => groupedCollection.First("I do not exist"); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [TestMethod] + public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + var target = groupedCollection.AddGroup("B", 10); + groupedCollection.AddGroup("B", 42); + + var result = groupedCollection.FirstOrDefault("B"); + + result.Should().BeSameAs(target); + } + + [TestCategory("Collections")] + [TestMethod] + public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + + var result = groupedCollection.FirstOrDefault("I do not exist"); + + result.Should().BeNull(); + } + + [TestCategory("Collections")] + [TestMethod] + public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + groupedCollection.AddGroup("B", 10, 11, 12); + groupedCollection.AddGroup("B", 42); + + var result = groupedCollection.ElementAt("B", 2); + + result.Should().Be(12); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(-1)] + [DataRow(3)] + public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int index) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + groupedCollection.AddGroup("B", 10, 11, 12); + groupedCollection.AddGroup("B", 42); + + Action action = () => groupedCollection.ElementAt("B", index); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [TestMethod] + public void ElementAt_WhenGroupDoesNotExist_ShouldThrow() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + + Action action = () => groupedCollection.ElementAt("I do not exist", 0); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [TestMethod] + public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + groupedCollection.AddGroup("B", 10, 11, 12); + groupedCollection.AddGroup("B", 42); + + var result = groupedCollection.ElementAt("B", 2); + + result.Should().Be(12); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(-1)] + [DataRow(3)] + public void ElementAtOrDefault_WhenGroupExistsAndIndexOutOfRange_ShouldReturnDefaultValue(int index) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + groupedCollection.AddGroup("B", 10, 11, 12); + groupedCollection.AddGroup("B", 42); + + var result = groupedCollection.ElementAtOrDefault("B", index); + + result.Should().Be(0); + } + + [TestCategory("Collections")] + [TestMethod] + public void ElementAtOrDefault_WhenGroupDoesNotExist_ShouldReturnDefaultValue() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 23); + + var result = groupedCollection.ElementAtOrDefault("I do not exist", 0); + + result.Should().Be(0); + } + + [TestCategory("Collections")] + [TestMethod] + public void AddGroup_WithItem_ShouldAddGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + + var addedGroup = groupedCollection.AddGroup("new key", 23); + + addedGroup.Should().NotBeNull(); + addedGroup.Key.Should().Be("new key"); + addedGroup.Should().ContainSingle(); + addedGroup.Should().ContainInOrder(23); + + groupedCollection.Should().ContainSingle(); + groupedCollection.Should().HaveElementAt(0, addedGroup); + } + + [TestCategory("Collections")] + [TestMethod] + public void AddGroup_WithCollection_ShouldAddGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + + var addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 }); + + addedGroup.Should().NotBeNull(); + addedGroup.Key.Should().Be("new key"); + addedGroup.Should().HaveCount(3); + addedGroup.Should().ContainInOrder(23, 10, 42); + + groupedCollection.Should().ContainSingle(); + groupedCollection.Should().HaveElementAt(0, addedGroup); + } + + [TestCategory("Collections")] + [TestMethod] + public void AddGroup_WithParamsCollection_ShouldAddGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + + var addedGroup = groupedCollection.AddGroup("new key", 23, 10, 42); + + addedGroup.Should().NotBeNull(); + addedGroup.Key.Should().Be("new key"); + addedGroup.Should().HaveCount(3); + addedGroup.Should().ContainInOrder(23, 10, 42); + + groupedCollection.Should().ContainSingle(); + groupedCollection.Should().HaveElementAt(0, addedGroup); + } + + [TestCategory("Collections")] + [TestMethod] + public void AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + + var addedGroup = groupedCollection.AddItem("new key", 23); + + addedGroup.Should().NotBeNull(); + addedGroup.Key.Should().Be("new key"); + addedGroup.Should().ContainSingle(); + addedGroup.Should().ContainInOrder(23); + + groupedCollection.Should().ContainSingle(); + groupedCollection.Should().HaveElementAt(0, addedGroup); + } + + [TestCategory("Collections")] + [TestMethod] + public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + var targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + groupedCollection.AddGroup("C", 7, 8); + + var addedGroup = groupedCollection.AddItem("B", 23); + + addedGroup.Should().BeSameAs(targetGroup); + addedGroup.Key.Should().Be("B"); + addedGroup.Should().HaveCount(4); + addedGroup.Should().ContainInOrder(4, 5, 6, 23); + + groupedCollection.Should().HaveCount(3); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(4); + groupedCollection.ElementAt(1).Should().ContainInOrder(4, 5, 6, 23); + + groupedCollection.ElementAt(2).Key.Should().Be("C"); + groupedCollection.ElementAt(2).Should().HaveCount(2); + groupedCollection.ElementAt(2).Should().ContainInOrder(7, 8); + } + + [TestCategory("Collections")] + [TestMethod] + public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExistingGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + var targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + groupedCollection.AddGroup("B", 7, 8, 9); + groupedCollection.AddGroup("C", 10, 11); + + var addedGroup = groupedCollection.AddItem("B", 23); + + addedGroup.Should().BeSameAs(targetGroup); + addedGroup.Key.Should().Be("B"); + addedGroup.Should().HaveCount(4); + addedGroup.Should().ContainInOrder(4, 5, 6, 23); + + groupedCollection.Should().HaveCount(4); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(4); + groupedCollection.ElementAt(1).Should().ContainInOrder(4, 5, 6, 23); + + groupedCollection.ElementAt(2).Key.Should().Be("B"); + groupedCollection.ElementAt(2).Should().HaveCount(3); + groupedCollection.ElementAt(2).Should().ContainInOrder(7, 8, 9); + + groupedCollection.ElementAt(3).Key.Should().Be("C"); + groupedCollection.ElementAt(3).Should().HaveCount(2); + groupedCollection.ElementAt(3).Should().ContainInOrder(10, 11); + } + + [TestCategory("Collections")] + [TestMethod] + public void InsertItem_WhenGroupDoesNotExist_ShoudThrow() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + + Action action = () => groupedCollection.InsertItem("I do not exist", 0, 23); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(-1)] + [DataRow(4)] + public void InsertItem_WhenIndexOutOfRange_ShoudThrow(int index) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + + Action action = () => groupedCollection.InsertItem("A", index, 23); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(0, new[] { 23, 1, 2, 3 })] + [DataRow(1, new[] { 1, 23, 2, 3 })] + [DataRow(3, new[] { 1, 2, 3, 23 })] + public void InsertItem_WithValidIndex_WithSeveralGroups_ShoudInsertItemInFirstGroup(int index, int[] expecteGroupValues) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 4, 5); + var targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); + groupedCollection.AddGroup("B", 6, 7); + + var group = groupedCollection.InsertItem("B", index, 23); + + group.Should().BeSameAs(targetGroup); + + groupedCollection.Should().HaveCount(3); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(2); + groupedCollection.ElementAt(0).Should().ContainInOrder(4, 5); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(4); + groupedCollection.ElementAt(1).Should().ContainInOrder(expecteGroupValues); + + groupedCollection.ElementAt(2).Key.Should().Be("B"); + groupedCollection.ElementAt(2).Should().HaveCount(2); + groupedCollection.ElementAt(2).Should().ContainInOrder(6, 7); + } + + [TestCategory("Collections")] + [TestMethod] + public void SetItem_WhenGroupDoesNotExist_ShoudThrow() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + + Action action = () => groupedCollection.SetItem("I do not exist", 0, 23); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(-1)] + [DataRow(3)] + public void SetItem_WhenIndexOutOfRange_ShoudThrow(int index) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + + Action action = () => groupedCollection.SetItem("A", index, 23); + + action.Should().Throw(); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(0, new[] { 23, 2, 3 })] + [DataRow(1, new[] { 1, 23, 3 })] + [DataRow(2, new[] { 1, 2, 23 })] + public void SetItem_WithValidIndex_WithSeveralGroups_ShoudReplaceItemInFirstGroup(int index, int[] expecteGroupValues) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 4, 5); + var targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); + groupedCollection.AddGroup("B", 6, 7); + + var group = groupedCollection.SetItem("B", index, 23); + + group.Should().BeSameAs(targetGroup); + + groupedCollection.Should().HaveCount(3); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(2); + groupedCollection.ElementAt(0).Should().ContainInOrder(4, 5); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(3); + groupedCollection.ElementAt(1).Should().ContainInOrder(expecteGroupValues); + + groupedCollection.ElementAt(2).Key.Should().Be("B"); + groupedCollection.ElementAt(2).Should().HaveCount(2); + groupedCollection.ElementAt(2).Should().ContainInOrder(6, 7); + } + + [TestCategory("Collections")] + [TestMethod] + public void RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + + groupedCollection.RemoveGroup("I do not exist"); + + groupedCollection.Should().ContainSingle(); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + } + + [TestCategory("Collections")] + [TestMethod] + public void RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + groupedCollection.AddGroup("B", 4, 5, 6); + + groupedCollection.RemoveGroup("B"); + + groupedCollection.Should().ContainSingle(); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + } + + [TestCategory("Collections")] + [TestMethod] + public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + groupedCollection.AddGroup("B", 4, 5, 6); + groupedCollection.AddGroup("B", 7, 8); + + groupedCollection.RemoveGroup("B"); + + groupedCollection.Should().HaveCount(2); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(2); + groupedCollection.ElementAt(1).Should().ContainInOrder(7, 8); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + groupedCollection.AddGroup("B", 4, 5, 6); + + groupedCollection.RemoveItem("I do not exist", 8, removeGroupIfEmpty); + + groupedCollection.Should().HaveCount(2); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(3); + groupedCollection.ElementAt(1).Should().ContainInOrder(4, 5, 6); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + groupedCollection.AddGroup("B", 4, 5, 6); + + groupedCollection.RemoveItem("B", 8, removeGroupIfEmpty); + + groupedCollection.Should().HaveCount(2); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(3); + groupedCollection.ElementAt(1).Should().ContainInOrder(4, 5, 6); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(true)] + [DataRow(false)] + public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool removeGroupIfEmpty) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + groupedCollection.AddGroup("B", 4, 5, 6); + + groupedCollection.RemoveItem("B", 5, removeGroupIfEmpty); + + groupedCollection.Should().HaveCount(2); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().HaveCount(2); + groupedCollection.ElementAt(1).Should().ContainInOrder(4, 6); + } + + [TestCategory("Collections")] + [DataTestMethod] + [DataRow(true, true)] + [DataRow(false, false)] + public void RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool removeGroupIfEmpty, bool expectGroupRemoved) + { + var groupedCollection = new ObservableGroupedCollection(); + groupedCollection.AddGroup("A", 1, 2, 3); + groupedCollection.AddGroup("B", 4); + + groupedCollection.RemoveItem("B", 4, removeGroupIfEmpty); + + groupedCollection.Should().HaveCount(expectGroupRemoved ? 1 : 2); + groupedCollection.ElementAt(0).Key.Should().Be("A"); + groupedCollection.ElementAt(0).Should().HaveCount(3); + groupedCollection.ElementAt(0).Should().ContainInOrder(1, 2, 3); + + if (!expectGroupRemoved) + { + groupedCollection.ElementAt(1).Key.Should().Be("B"); + groupedCollection.ElementAt(1).Should().BeEmpty(); + } + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index ce3edb2b442..de1d3dd3f60 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -123,6 +123,7 @@ +