From 23fee05dd28e327ea962f420c8398b9dbacc523f Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 00:07:06 +0200 Subject: [PATCH 01/10] add add/remove extension methods --- .../ObservableGroupedCollectionExtensions.cs | 190 ++++++++++++ ...ervableGroupedCollectionExtensionsTests.cs | 290 ++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 3 files changed, 481 insertions(+) create mode 100644 Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs create mode 100644 UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs new file mode 100644 index 00000000000..fbd764ab30f --- /dev/null +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -0,0 +1,190 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Microsoft.Toolkit.Collections +{ + /// + /// The extensions methods to simplify the usage of . + /// + public static class ObservableGroupedCollectionExtensions + { + /// + /// 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 to add. + /// 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 to add. + /// 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 to add. + /// 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 group with key. + /// If the group does not exist, it will be added. + /// If several groups exist with the same key, will be added to the first group. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key to add. + /// 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(group => GroupKeyPredicate(group, key)); + if (existingGroup is null) + { + existingGroup = new ObservableGroup(key); + source.Add(existingGroup); + } + + existingGroup.Add(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 item 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 item to remove. + /// 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 item to remove. + /// 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..23599dff3cf --- /dev/null +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -0,0 +1,290 @@ +// 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.Linq; + +namespace UnitTests.Collections +{ + [TestClass] + public class ObservableGroupedCollectionExtensionsTests + { + [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 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 @@ + From 4c6dc72d334e7efa223525d67f46f43835232525 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 22:27:03 +0200 Subject: [PATCH 02/10] Add insertitem method --- .../ObservableGroupedCollectionExtensions.cs | 32 ++++++++++- ...ervableGroupedCollectionExtensionsTests.cs | 57 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index fbd764ab30f..0092c23166a 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -2,6 +2,7 @@ // 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; @@ -63,9 +64,8 @@ public static ObservableGroup AddGroup( } /// - /// Add into the group with key. + /// Add into the first group with key. /// If the group does not exist, it will be added. - /// If several groups exist with the same key, will be added to the first group. /// /// The type of the group key. /// The type of the items in the collection. @@ -89,6 +89,34 @@ public static ObservableGroup AddItem( 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 to add. + /// 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(group => GroupKeyPredicate(group, key)); + if (existingGroup is null) + { + throw new InvalidOperationException(); + } + + existingGroup.Insert(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. diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 23599dff3cf..17ff400f743 100644 --- a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Microsoft.Toolkit.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Linq; namespace UnitTests.Collections @@ -145,6 +146,62 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis 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 RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() From c0ed2a418fe79bcd312e9ad331461bc546e89705 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 22:42:21 +0200 Subject: [PATCH 03/10] add SetItem + fix doc --- .../ObservableGroupedCollectionExtensions.cs | 44 ++++++++++++--- ...ervableGroupedCollectionExtensionsTests.cs | 56 +++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index 0092c23166a..c76aac7ce8b 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class ObservableGroupedCollectionExtensions /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// The key of the group where will be added. /// The value to add. /// The added . public static ObservableGroup AddGroup( @@ -34,7 +34,7 @@ public static ObservableGroup AddGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// The key of the group where will be added. /// The collection to add. /// The added . public static ObservableGroup AddGroup( @@ -49,7 +49,7 @@ public static ObservableGroup AddGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// The key of the group where will be added. /// The collection to add. /// The added . public static ObservableGroup AddGroup( @@ -70,7 +70,7 @@ public static ObservableGroup AddGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// 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( @@ -95,7 +95,7 @@ public static ObservableGroup AddItem( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// 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. @@ -117,6 +117,34 @@ public static ObservableGroup InsertItem( 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(group => GroupKeyPredicate(group, key)); + if (existingGroup is null) + { + throw new InvalidOperationException(); + } + + 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. @@ -124,7 +152,7 @@ public static ObservableGroup InsertItem( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key of the item to remove. + /// The key of the group to remove. public static void RemoveGroup( this ObservableGroupedCollection source, TKey key) @@ -149,7 +177,7 @@ public static void RemoveGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key of the item to remove. + /// 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( @@ -184,7 +212,7 @@ public static void RemoveItem( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key of the item to remove. + /// The key of the group where the 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( diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 17ff400f743..7c09a06a380 100644 --- a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -202,6 +202,62 @@ public void InsertItem_WithValidIndex_WithSeveralGroups_ShoudInsertItemInFirstGr 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() From 48a93cd863ba25e682122b6f00efab846d11c417 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 23:12:02 +0200 Subject: [PATCH 04/10] add first and elementat methods --- .../ObservableGroupedCollectionExtensions.cs | 87 ++++++++--- ...ervableGroupedCollectionExtensionsTests.cs | 136 ++++++++++++++++++ 2 files changed, 206 insertions(+), 17 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index c76aac7ce8b..9769248d45d 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -13,6 +13,69 @@ namespace Microsoft.Toolkit.Collections /// 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 . /// @@ -23,10 +86,10 @@ public static class ObservableGroupedCollectionExtensions /// The value to add. /// The added . public static ObservableGroup AddGroup( - this ObservableGroupedCollection source, - TKey key, - TValue value) - => AddGroup(source, key, new[] { value }); + this ObservableGroupedCollection source, + TKey key, + TValue value) + => AddGroup(source, key, new[] { value }); /// /// Adds a key-collection item into a target . @@ -78,7 +141,7 @@ public static ObservableGroup AddItem( TKey key, TValue item) { - var existingGroup = source.FirstOrDefault(group => GroupKeyPredicate(group, key)); + var existingGroup = source.FirstOrDefault(key); if (existingGroup is null) { existingGroup = new ObservableGroup(key); @@ -107,12 +170,7 @@ public static ObservableGroup InsertItem( int index, TValue item) { - var existingGroup = source.First(group => GroupKeyPredicate(group, key)); - if (existingGroup is null) - { - throw new InvalidOperationException(); - } - + var existingGroup = source.First(key); existingGroup.Insert(index, item); return existingGroup; } @@ -135,12 +193,7 @@ public static ObservableGroup SetItem( int index, TValue item) { - var existingGroup = source.First(group => GroupKeyPredicate(group, key)); - if (existingGroup is null) - { - throw new InvalidOperationException(); - } - + var existingGroup = source.First(key); existingGroup[index] = item; return existingGroup; } diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 7c09a06a380..0c1128be410 100644 --- a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -13,6 +13,142 @@ 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() From 9b8d49d0819eaebc83c96a914eb48348b75efaa5 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 00:07:06 +0200 Subject: [PATCH 05/10] add add/remove extension methods --- .../ObservableGroupedCollectionExtensions.cs | 190 ++++++++++++ ...ervableGroupedCollectionExtensionsTests.cs | 290 ++++++++++++++++++ UnitTests/UnitTests.csproj | 1 + 3 files changed, 481 insertions(+) create mode 100644 Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs create mode 100644 UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs new file mode 100644 index 00000000000..fbd764ab30f --- /dev/null +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -0,0 +1,190 @@ +// 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.Collections.Generic; +using System.Linq; + +namespace Microsoft.Toolkit.Collections +{ + /// + /// The extensions methods to simplify the usage of . + /// + public static class ObservableGroupedCollectionExtensions + { + /// + /// 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 to add. + /// 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 to add. + /// 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 to add. + /// 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 group with key. + /// If the group does not exist, it will be added. + /// If several groups exist with the same key, will be added to the first group. + /// + /// The type of the group key. + /// The type of the items in the collection. + /// The source instance. + /// The key to add. + /// 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(group => GroupKeyPredicate(group, key)); + if (existingGroup is null) + { + existingGroup = new ObservableGroup(key); + source.Add(existingGroup); + } + + existingGroup.Add(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 item 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 item to remove. + /// 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 item to remove. + /// 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..23599dff3cf --- /dev/null +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -0,0 +1,290 @@ +// 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.Linq; + +namespace UnitTests.Collections +{ + [TestClass] + public class ObservableGroupedCollectionExtensionsTests + { + [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 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 @@ + From cc23413a1a709397e521209eb237c8baa9f3ae56 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 22:27:03 +0200 Subject: [PATCH 06/10] Add insertitem method --- .../ObservableGroupedCollectionExtensions.cs | 32 ++++++++++- ...ervableGroupedCollectionExtensionsTests.cs | 57 +++++++++++++++++++ 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index fbd764ab30f..0092c23166a 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -2,6 +2,7 @@ // 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; @@ -63,9 +64,8 @@ public static ObservableGroup AddGroup( } /// - /// Add into the group with key. + /// Add into the first group with key. /// If the group does not exist, it will be added. - /// If several groups exist with the same key, will be added to the first group. /// /// The type of the group key. /// The type of the items in the collection. @@ -89,6 +89,34 @@ public static ObservableGroup AddItem( 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 to add. + /// 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(group => GroupKeyPredicate(group, key)); + if (existingGroup is null) + { + throw new InvalidOperationException(); + } + + existingGroup.Insert(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. diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 23599dff3cf..17ff400f743 100644 --- a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Microsoft.Toolkit.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; using System.Linq; namespace UnitTests.Collections @@ -145,6 +146,62 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis 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 RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() From df9baff1cc26aabe8313291b0d1af774bc1ef0ea Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 22:42:21 +0200 Subject: [PATCH 07/10] add SetItem + fix doc --- .../ObservableGroupedCollectionExtensions.cs | 44 ++++++++++++--- ...ervableGroupedCollectionExtensionsTests.cs | 56 +++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index 0092c23166a..c76aac7ce8b 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class ObservableGroupedCollectionExtensions /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// The key of the group where will be added. /// The value to add. /// The added . public static ObservableGroup AddGroup( @@ -34,7 +34,7 @@ public static ObservableGroup AddGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// The key of the group where will be added. /// The collection to add. /// The added . public static ObservableGroup AddGroup( @@ -49,7 +49,7 @@ public static ObservableGroup AddGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// The key of the group where will be added. /// The collection to add. /// The added . public static ObservableGroup AddGroup( @@ -70,7 +70,7 @@ public static ObservableGroup AddGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// 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( @@ -95,7 +95,7 @@ public static ObservableGroup AddItem( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key to add. + /// 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. @@ -117,6 +117,34 @@ public static ObservableGroup InsertItem( 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(group => GroupKeyPredicate(group, key)); + if (existingGroup is null) + { + throw new InvalidOperationException(); + } + + 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. @@ -124,7 +152,7 @@ public static ObservableGroup InsertItem( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key of the item to remove. + /// The key of the group to remove. public static void RemoveGroup( this ObservableGroupedCollection source, TKey key) @@ -149,7 +177,7 @@ public static void RemoveGroup( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key of the item to remove. + /// 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( @@ -184,7 +212,7 @@ public static void RemoveItem( /// The type of the group key. /// The type of the items in the collection. /// The source instance. - /// The key of the item to remove. + /// The key of the group where the 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( diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 17ff400f743..7c09a06a380 100644 --- a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -202,6 +202,62 @@ public void InsertItem_WithValidIndex_WithSeveralGroups_ShoudInsertItemInFirstGr 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() From 3c8b72c417c67428ff1a367fa25d5c2e14dbdc02 Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 21 Apr 2020 23:12:02 +0200 Subject: [PATCH 08/10] add first and elementat methods --- .../ObservableGroupedCollectionExtensions.cs | 87 ++++++++--- ...ervableGroupedCollectionExtensionsTests.cs | 136 ++++++++++++++++++ 2 files changed, 206 insertions(+), 17 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index c76aac7ce8b..9769248d45d 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -13,6 +13,69 @@ namespace Microsoft.Toolkit.Collections /// 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 . /// @@ -23,10 +86,10 @@ public static class ObservableGroupedCollectionExtensions /// The value to add. /// The added . public static ObservableGroup AddGroup( - this ObservableGroupedCollection source, - TKey key, - TValue value) - => AddGroup(source, key, new[] { value }); + this ObservableGroupedCollection source, + TKey key, + TValue value) + => AddGroup(source, key, new[] { value }); /// /// Adds a key-collection item into a target . @@ -78,7 +141,7 @@ public static ObservableGroup AddItem( TKey key, TValue item) { - var existingGroup = source.FirstOrDefault(group => GroupKeyPredicate(group, key)); + var existingGroup = source.FirstOrDefault(key); if (existingGroup is null) { existingGroup = new ObservableGroup(key); @@ -107,12 +170,7 @@ public static ObservableGroup InsertItem( int index, TValue item) { - var existingGroup = source.First(group => GroupKeyPredicate(group, key)); - if (existingGroup is null) - { - throw new InvalidOperationException(); - } - + var existingGroup = source.First(key); existingGroup.Insert(index, item); return existingGroup; } @@ -135,12 +193,7 @@ public static ObservableGroup SetItem( int index, TValue item) { - var existingGroup = source.First(group => GroupKeyPredicate(group, key)); - if (existingGroup is null) - { - throw new InvalidOperationException(); - } - + var existingGroup = source.First(key); existingGroup[index] = item; return existingGroup; } diff --git a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 7c09a06a380..0c1128be410 100644 --- a/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -13,6 +13,142 @@ 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() From b62781a12ea3d69f45c0464f886ab9f47cd3c5ea Mon Sep 17 00:00:00 2001 From: Vincent Date: Thu, 23 Apr 2020 00:55:07 +0200 Subject: [PATCH 09/10] fix documentation --- .../Collections/ObservableGroupedCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index 9769248d45d..bd38897302c 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -265,7 +265,7 @@ public static void RemoveItem( /// 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 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( From 60af7ba08a3136d9081dae45b1be3a0879044331 Mon Sep 17 00:00:00 2001 From: Vincent Gromfeld Date: Thu, 30 Apr 2020 11:59:35 +0200 Subject: [PATCH 10/10] fix alignment --- .../Collections/ObservableGroupedCollectionExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs index bd38897302c..8be7dacec73 100644 --- a/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs +++ b/Microsoft.Toolkit/Collections/ObservableGroupedCollectionExtensions.cs @@ -86,9 +86,9 @@ public static TValue ElementAtOrDefault( /// The value to add. /// The added . public static ObservableGroup AddGroup( - this ObservableGroupedCollection source, - TKey key, - TValue value) + this ObservableGroupedCollection source, + TKey key, + TValue value) => AddGroup(source, key, new[] { value }); ///