From 257250dc70cf69fde37f199e6545fc629315df13 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 12 Mar 2022 01:02:09 +0100 Subject: [PATCH 01/22] Add IReadOnlyObservableGroup --- .../IReadOnlyObservableGroup{TKey}.cs | 18 ++++++++++++++++++ .../ObservableGroup{TKey,TValue}.cs | 2 +- .../ReadOnlyObservableGroup{TKey,TValue}.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs diff --git a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs b/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs new file mode 100644 index 000000000..8eae3e812 --- /dev/null +++ b/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey}.cs @@ -0,0 +1,18 @@ +// 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. + +namespace CommunityToolkit.Mvvm.Collections; + +/// +/// An interface for a grouped collection of items. +/// +/// The type of the group key. +public interface IReadOnlyObservableGroup : IReadOnlyObservableGroup + where TKey : notnull +{ + /// + /// Gets the key for the current collection. + /// + new TKey Key { get; } +} diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs index 71fdf782f..ecdaa48d3 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// The type of the group key. /// The type of the items in the collection. [DebuggerDisplay("Key = {Key}, Count = {Count}")] -public class ObservableGroup : ObservableCollection, IGrouping, IReadOnlyObservableGroup +public class ObservableGroup : ObservableCollection, IGrouping, IReadOnlyObservableGroup where TKey : notnull { /// diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs index 304e7e54d..d8c823512 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// The type of the group key. /// The type of the items in the collection. -public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IGrouping, IReadOnlyObservableGroup +public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IGrouping, IReadOnlyObservableGroup where TKey : notnull { /// From 186ff01aebb502a97c5600690a3d9bbc8c0ef61d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 12 Mar 2022 14:54:07 +0100 Subject: [PATCH 02/22] Remove unnecessary constructor --- ...bservableGroupedCollection{TKey,TValue}.cs | 9 ------- .../ReadOnlyObservableGroup{TKey,TValue}.cs | 12 --------- .../ReadOnlyObservableGroupTests.cs | 2 +- ...eadOnlyObservableGroupedCollectionTests.cs | 25 +++---------------- 4 files changed, 4 insertions(+), 44 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs index f5be9a2e6..825dc76ae 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs @@ -38,15 +38,6 @@ public ReadOnlyObservableGroupedCollection(IEnumerable - /// Initializes a new instance of the class. - /// - /// The initial data to add in the grouped collection. - public ReadOnlyObservableGroupedCollection(IEnumerable> collection) - : this(collection.Select(static g => new ReadOnlyObservableGroup(g.Key, g))) - { - } - private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { // Even if NotifyCollectionChangedEventArgs allows multiple items, the actual implementation diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs index d8c823512..30dbd429e 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs @@ -2,7 +2,6 @@ // 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.Collections.ObjectModel; using System.Linq; @@ -37,17 +36,6 @@ public ReadOnlyObservableGroup(ObservableGroup group) Key = group.Key; } - /// - /// Initializes a new instance of the class. - /// - /// The key of the group. - /// The collection of items to add in the group. - public ReadOnlyObservableGroup(TKey key, IEnumerable collection) - : base(new ObservableCollection(collection)) - { - Key = key; - } - /// public TKey Key { get; } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs index c85c025d6..3e08f299b 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs @@ -38,7 +38,7 @@ public void Ctor_ObservableGroup_ShouldHaveExpectedInitialState() [TestMethod] public void Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState() { - int[]? source = new[] { 1, 2, 3 }; + ObservableCollection source = new() { 1, 2, 3 }; ReadOnlyObservableGroup? group = new("key", source); Assert.AreEqual(group.Key, "key"); diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs index 50a261c4a..d105fd1d6 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs @@ -47,32 +47,13 @@ public void Ctor_WithObservableGroupedCollection_ShoudInitializeObject() CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(1), new[] { 2, 4, 6 }); } - [TestMethod] - public void Ctor_WithListOfIGroupingSource_ShoudInitializeObject() - { - List>? source = new() - { - new IntGroup("A", new[] { 1, 3, 5 }), - new IntGroup("B", new[] { 2, 4, 6 }), - }; - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); - - Assert.AreEqual(readOnlyGroup.Count, 2); - - Assert.AreEqual(readOnlyGroup.ElementAt(0).Key, "A"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(0), new[] { 1, 3, 5 }); - - Assert.AreEqual(readOnlyGroup.ElementAt(1).Key, "B"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(1), new[] { 2, 4, 6 }); - } - [TestMethod] public void Ctor_WithListOfReadOnlyObservableGroupSource_ShoudInitializeObject() { - List>? source = new() + ObservableCollection>? source = new() { - new ReadOnlyObservableGroup("A", new[] { 1, 3, 5 }), - new ReadOnlyObservableGroup("B", new[] { 2, 4, 6 }), + new ReadOnlyObservableGroup("A", new ObservableCollection { 1, 3, 5 }), + new ReadOnlyObservableGroup("B", new ObservableCollection { 2, 4, 6 }), }; ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); From 684a650059b8faf0d3bc4ea1155588367d0577b9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 12 Mar 2022 14:57:40 +0100 Subject: [PATCH 03/22] Improve caching of KeyChangedEventArgs --- .../Internals/ObservableGroupHelper.cs | 18 ++++++++++++++++++ .../ObservableGroup{TKey,TValue}.cs | 9 ++------- 2 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs diff --git a/CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs b/CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs new file mode 100644 index 000000000..3b4581a42 --- /dev/null +++ b/CommunityToolkit.Mvvm/Collections/Internals/ObservableGroupHelper.cs @@ -0,0 +1,18 @@ +// 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.ComponentModel; + +namespace CommunityToolkit.Mvvm.Collections.Internals; + +/// +/// A helper type for the type. +/// +internal static class ObservableGroupHelper +{ + /// + /// The cached for + /// + public static readonly PropertyChangedEventArgs KeyChangedEventArgs = new(nameof(IReadOnlyObservableGroup.Key)); +} \ No newline at end of file diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs index ecdaa48d3..75cc0a23e 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; using System.Diagnostics; using System.Linq; +using CommunityToolkit.Mvvm.Collections.Internals; namespace CommunityToolkit.Mvvm.Collections; @@ -20,11 +20,6 @@ namespace CommunityToolkit.Mvvm.Collections; public class ObservableGroup : ObservableCollection, IGrouping, IReadOnlyObservableGroup where TKey : notnull { - /// - /// The cached for - /// - private static readonly PropertyChangedEventArgs KeyChangedEventArgs = new(nameof(Key)); - /// /// Initializes a new instance of the class. /// @@ -69,7 +64,7 @@ public TKey Key { this.key = value; - OnPropertyChanged(KeyChangedEventArgs); + OnPropertyChanged(ObservableGroupHelper.KeyChangedEventArgs); } } } From 6d2a502334d8fc017aac341df039090d7449eef5 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 12 Mar 2022 15:11:44 +0100 Subject: [PATCH 04/22] Improve ReadOnlyObservableGroupedCollection constructors --- ...dOnlyObservableGroupedCollection{TKey,TValue}.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs index 825dc76ae..4e2cad20d 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; @@ -23,18 +22,18 @@ public sealed class ReadOnlyObservableGroupedCollection : ReadOnly /// Initializes a new instance of the class. /// /// The source collection to wrap. - public ReadOnlyObservableGroupedCollection(ObservableGroupedCollection collection) - : this(collection.Select(static g => new ReadOnlyObservableGroup(g))) + public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) + : base(new ObservableCollection>(collection.Select(static g => new ReadOnlyObservableGroup(g)))) { - ((INotifyCollectionChanged)collection).CollectionChanged += OnSourceCollectionChanged; + collection.CollectionChanged += OnSourceCollectionChanged; } /// /// Initializes a new instance of the class. /// - /// The initial data to add in the grouped collection. - public ReadOnlyObservableGroupedCollection(IEnumerable> collection) - : base(new ObservableCollection>(collection)) + /// The source collection to wrap. + public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) + : base(collection) { } From 2d582421edb7930cb46a33104cbf03a996025a9f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 12 Mar 2022 15:40:52 +0100 Subject: [PATCH 05/22] Improvements to ReadOnlyObservableGroupedCollection logic --- ...bservableGroupedCollection{TKey,TValue}.cs | 95 +++++++++++++------ 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs index 4e2cad20d..6e63bdba0 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs @@ -5,7 +5,7 @@ using System; using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace CommunityToolkit.Mvvm.Collections; @@ -37,56 +37,93 @@ public ReadOnlyObservableGroupedCollection(ObservableCollection + /// Forwards the event whenever it is raised by the wrapped collection. + /// + /// The wrapped collection (an of instance). + /// The arguments. + /// Thrown if a range operation is requested. private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { // Even if NotifyCollectionChangedEventArgs allows multiple items, the actual implementation - // is only reporting the changes one by one. We consider only this case for now. - if (e.OldItems?.Count > 1 || e.NewItems?.Count > 1) + // is only reporting the changes one by one. We consider only this case for now. If this is + // added in a new version of .NET, this type will need to be updated accordingly in a new version. + [DoesNotReturn] + static void ThrowNotSupportedExceptionForRangeOperation() { - static void ThrowNotSupportedException() - { - throw new NotSupportedException( - "ReadOnlyObservableGroupedCollection doesn't support operations on multiple items at once.\n" + - "If this exception was thrown, it likely means support for batched item updates has been added to the " + - "underlying ObservableCollection type, and this implementation doesn't support that feature yet.\n" + - "Please consider opening an issue in https://aka.ms/windowstoolkit to report this."); - } - - ThrowNotSupportedException(); + throw new NotSupportedException( + "ReadOnlyObservableGroupedCollection doesn't support operations on multiple items at once.\n" + + "If this exception was thrown, it likely means support for batched item updates has been added to the " + + "underlying ObservableCollection type, and this implementation doesn't support that feature yet.\n" + + "Please consider opening an issue in https://aka.ms/toolkit/dotnet to report this."); } + // The inner Items list is ObservableCollection>, so doing a direct cast here will always succeed + ObservableCollection> items = (ObservableCollection>)Items; + switch (e.Action) { - case NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Replace: + // Insert a single item for an "Add" operation, fail if multiple items are added + case NotifyCollectionChangedAction.Add: + if (e.NewItems!.Count == 1) + { + ObservableGroup newItem = (ObservableGroup)e.NewItems![0]!; - // We only need to find the new item if the operation is either add or remove. In this - // case we just directly find the first item that was modified, or throw if it's not present. - // This normally never happens anyway - add and replace should always have a target element. - ObservableGroup newItem = e.NewItems!.Cast>().First(); + items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup(newItem)); + } + else if (e.NewItems!.Count > 1) + { + ThrowNotSupportedExceptionForRangeOperation(); + } - if (e.Action == NotifyCollectionChangedAction.Add) + break; + + // Remove a single item at offset for a "Remove" operation, fail if multiple items are removed + case NotifyCollectionChangedAction.Remove: + if (e.OldItems!.Count == 1) { - Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup(newItem)); + items.RemoveAt(e.OldStartingIndex); } - else + else if (e.OldItems!.Count > 1) { - Items[e.OldStartingIndex] = new ReadOnlyObservableGroup(newItem); + ThrowNotSupportedExceptionForRangeOperation(); } break; - case NotifyCollectionChangedAction.Move: - // Our inner Items list is our own ObservableCollection> so we can safely cast Items to its concrete type here. - ((ObservableCollection>)Items).Move(e.OldStartingIndex, e.NewStartingIndex); + // Replace a single item at offset for a "Replace" operation, fail if multiple items are replaced + case NotifyCollectionChangedAction.Replace: + if (e.OldItems!.Count == 1 && e.NewItems!.Count == 1) + { + ObservableGroup replacedItem = (ObservableGroup)e.NewItems![0]!; + + items[e.OldStartingIndex] = new ReadOnlyObservableGroup(replacedItem); + } + else if (e.OldItems!.Count > 1 || e.NewItems!.Count > 1) + { + ThrowNotSupportedExceptionForRangeOperation(); + } + break; - case NotifyCollectionChangedAction.Remove: - Items.RemoveAt(e.OldStartingIndex); + + // Move a single item between offsets for a "Move" operation, fail if multiple items are moved + case NotifyCollectionChangedAction.Move: + if (e.OldItems!.Count == 1 && e.NewItems!.Count == 1) + { + items.Move(e.OldStartingIndex, e.NewStartingIndex); + } + else if (e.OldItems!.Count > 1 || e.NewItems!.Count > 1) + { + ThrowNotSupportedExceptionForRangeOperation(); + } + break; + + // A "Reset" operation is just forwarded normally case NotifyCollectionChangedAction.Reset: - Items.Clear(); + items.Clear(); break; default: - Debug.Fail("unsupported value"); break; } } From d08e1b74d7807f9ba7b94eb41361e0b166fb9ddc Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 19 Mar 2022 19:13:09 +0100 Subject: [PATCH 06/22] Add new interfaces/members to IReadOnlyObservableGroup --- .../Collections/IReadOnlyObservableGroup.cs | 12 +++++++++++- .../Collections/ObservableGroup{TKey,TValue}.cs | 3 +++ .../ReadOnlyObservableGroup{TKey,TValue}.cs | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs b/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs index d7b9eda2b..4c55d736d 100644 --- a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs +++ b/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup.cs @@ -2,6 +2,8 @@ // 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; +using System.Collections.Specialized; using System.ComponentModel; namespace CommunityToolkit.Mvvm.Collections; @@ -9,7 +11,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// An interface for a grouped collection of items. /// -public interface IReadOnlyObservableGroup : INotifyPropertyChanged +public interface IReadOnlyObservableGroup : INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable { /// /// Gets the key for the current collection. @@ -20,4 +22,12 @@ public interface IReadOnlyObservableGroup : INotifyPropertyChanged /// Gets the number of items currently in the grouped collection. /// int Count { get; } + + /// + /// Gets the element at the specified index in the current collection. + /// + /// The zero-based index of the element to get. + /// The element at the specified index in the read-only list. + /// Thrown if the index is out of range. + object? this[int index] { get; } } diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs index 75cc0a23e..5b21d85ab 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs @@ -71,4 +71,7 @@ public TKey Key /// object IReadOnlyObservableGroup.Key => Key; + + /// + object? IReadOnlyObservableGroup.this[int index] => this[index]; } diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs index 30dbd429e..a1109a101 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs @@ -41,4 +41,7 @@ public ReadOnlyObservableGroup(ObservableGroup group) /// object IReadOnlyObservableGroup.Key => Key; + + /// + object? IReadOnlyObservableGroup.this[int index] => this[index]; } From a3be770bed1dbaa159e7cdd61c76c44f7d5584b0 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 19 Mar 2022 19:14:59 +0100 Subject: [PATCH 07/22] Add IReadOnlyObservableGroup --- ...IReadOnlyObservableGroup{TKey,TElement}.cs | 25 +++++++++++++++++++ .../ObservableGroup{TKey,TValue}.cs | 2 +- .../ReadOnlyObservableGroup{TKey,TValue}.cs | 3 +-- 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs diff --git a/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs new file mode 100644 index 000000000..08b92f07c --- /dev/null +++ b/CommunityToolkit.Mvvm/Collections/IReadOnlyObservableGroup{TKey,TElement}.cs @@ -0,0 +1,25 @@ +// 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 CommunityToolkit.Mvvm.Collections; + +/// +/// An interface for a grouped collection of items. +/// +/// The type of the group key. +/// The type of elements in the group. +public interface IReadOnlyObservableGroup : IReadOnlyObservableGroup, IReadOnlyList, IGrouping + where TKey : notnull +{ + /// + /// Gets the element at the specified index in the current collection. + /// + /// The zero-based index of the element to get. + /// The element at the specified index in the read-only list. + /// Thrown if the index is out of range. + new TElement this[int index] { get; } +} diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs index 5b21d85ab..7f03b8e20 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// The type of the group key. /// The type of the items in the collection. [DebuggerDisplay("Key = {Key}, Count = {Count}")] -public class ObservableGroup : ObservableCollection, IGrouping, IReadOnlyObservableGroup +public class ObservableGroup : ObservableCollection, IReadOnlyObservableGroup where TKey : notnull { /// diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs index a1109a101..d30c33595 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; -using System.Linq; namespace CommunityToolkit.Mvvm.Collections; @@ -12,7 +11,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// The type of the group key. /// The type of the items in the collection. -public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IGrouping, IReadOnlyObservableGroup +public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IReadOnlyObservableGroup where TKey : notnull { /// From b522487acba8114e7614f314af9af43a47087160 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 19 Mar 2022 19:17:47 +0100 Subject: [PATCH 08/22] Rename TValue type params to TElement --- ...rvableGroupedCollection{TKey,TElement}.cs} | 14 ++++++------ ...}.cs => ObservableGroup{TKey,TElement}.cs} | 8 +++---- ...rvableGroupedCollection{TKey,TElement}.cs} | 22 +++++++++---------- ...ReadOnlyObservableGroup{TKey,TElement}.cs} | 8 +++---- 4 files changed, 26 insertions(+), 26 deletions(-) rename CommunityToolkit.Mvvm/Collections/{ObservableGroupedCollection{TKey,TValue}.cs => ObservableGroupedCollection{TKey,TElement}.cs} (78%) rename CommunityToolkit.Mvvm/Collections/{ObservableGroup{TKey,TValue}.cs => ObservableGroup{TKey,TElement}.cs} (86%) rename CommunityToolkit.Mvvm/Collections/{ReadOnlyObservableGroupedCollection{TKey,TValue}.cs => ReadOnlyObservableGroupedCollection{TKey,TElement}.cs} (84%) rename CommunityToolkit.Mvvm/Collections/{ReadOnlyObservableGroup{TKey,TValue}.cs => ReadOnlyObservableGroup{TKey,TElement}.cs} (81%) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs similarity index 78% rename from CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TValue}.cs rename to CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs index 5c3da18b4..4cfc66dfa 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs @@ -13,9 +13,9 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// An observable list of observable groups. /// -/// The type of the group key. -/// The type of the items in the collection. -public sealed class ObservableGroupedCollection : ObservableCollection> +/// The type of the group keys. +/// The type of elements in the collection. +public sealed class ObservableGroupedCollection : ObservableCollection> where TKey : notnull { /// @@ -29,8 +29,8 @@ public ObservableGroupedCollection() /// Initializes a new instance of the class. /// /// The initial data to add in the grouped collection. - public ObservableGroupedCollection(IEnumerable> collection) - : base(collection.Select(static c => new ObservableGroup(c))) + public ObservableGroupedCollection(IEnumerable> collection) + : base(collection.Select(static c => new ObservableGroup(c))) { } @@ -40,9 +40,9 @@ public ObservableGroupedCollection(IEnumerable> collecti /// The resulting , if one was in use. /// Whether or not a instance has been found. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryGetList([NotNullWhen(true)] out List>? list) + internal bool TryGetList([NotNullWhen(true)] out List>? list) { - list = Items as List>; + list = Items as List>; return list is not null; } diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs similarity index 86% rename from CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs rename to CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs index 7f03b8e20..448dc47ca 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs @@ -15,9 +15,9 @@ namespace CommunityToolkit.Mvvm.Collections; /// It associates a to an . /// /// The type of the group key. -/// The type of the items in the collection. +/// The type of elements in the group. [DebuggerDisplay("Key = {Key}, Count = {Count}")] -public class ObservableGroup : ObservableCollection, IReadOnlyObservableGroup +public class ObservableGroup : ObservableCollection, IReadOnlyObservableGroup where TKey : notnull { /// @@ -33,7 +33,7 @@ public ObservableGroup(TKey key) /// Initializes a new instance of the class. /// /// The grouping to fill the group. - public ObservableGroup(IGrouping grouping) + public ObservableGroup(IGrouping grouping) : base(grouping) { this.key = grouping.Key; @@ -44,7 +44,7 @@ public ObservableGroup(IGrouping grouping) /// /// The key for the group. /// The initial collection of data to add to the group. - public ObservableGroup(TKey key, IEnumerable collection) + public ObservableGroup(TKey key, IEnumerable collection) : base(collection) { this.key = key; diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs similarity index 84% rename from CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs rename to CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs index 6e63bdba0..fc7b55860 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs @@ -13,17 +13,17 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// A read-only list of groups. /// -/// The type of the group key. -/// The type of the items in the collection. -public sealed class ReadOnlyObservableGroupedCollection : ReadOnlyObservableCollection> +/// The type of the group keys. +/// The type of elements in the collection. +public sealed class ReadOnlyObservableGroupedCollection : ReadOnlyObservableCollection> where TKey : notnull { /// /// Initializes a new instance of the class. /// /// The source collection to wrap. - public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) - : base(new ObservableCollection>(collection.Select(static g => new ReadOnlyObservableGroup(g)))) + public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) + : base(new ObservableCollection>(collection.Select(static g => new ReadOnlyObservableGroup(g)))) { collection.CollectionChanged += OnSourceCollectionChanged; } @@ -32,7 +32,7 @@ public ReadOnlyObservableGroupedCollection(ObservableCollection class. /// /// The source collection to wrap. - public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) + public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) : base(collection) { } @@ -59,7 +59,7 @@ static void ThrowNotSupportedExceptionForRangeOperation() } // The inner Items list is ObservableCollection>, so doing a direct cast here will always succeed - ObservableCollection> items = (ObservableCollection>)Items; + ObservableCollection> items = (ObservableCollection>)Items; switch (e.Action) { @@ -67,9 +67,9 @@ static void ThrowNotSupportedExceptionForRangeOperation() case NotifyCollectionChangedAction.Add: if (e.NewItems!.Count == 1) { - ObservableGroup newItem = (ObservableGroup)e.NewItems![0]!; + ObservableGroup newItem = (ObservableGroup)e.NewItems![0]!; - items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup(newItem)); + items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup(newItem)); } else if (e.NewItems!.Count > 1) { @@ -95,9 +95,9 @@ static void ThrowNotSupportedExceptionForRangeOperation() case NotifyCollectionChangedAction.Replace: if (e.OldItems!.Count == 1 && e.NewItems!.Count == 1) { - ObservableGroup replacedItem = (ObservableGroup)e.NewItems![0]!; + ObservableGroup replacedItem = (ObservableGroup)e.NewItems![0]!; - items[e.OldStartingIndex] = new ReadOnlyObservableGroup(replacedItem); + items[e.OldStartingIndex] = new ReadOnlyObservableGroup(replacedItem); } else if (e.OldItems!.Count > 1 || e.NewItems!.Count > 1) { diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs similarity index 81% rename from CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs rename to CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs index d30c33595..76dabe718 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TValue}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs @@ -10,8 +10,8 @@ namespace CommunityToolkit.Mvvm.Collections; /// A read-only observable group. It associates a to a . /// /// The type of the group key. -/// The type of the items in the collection. -public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IReadOnlyObservableGroup +/// The type of elements in the group. +public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IReadOnlyObservableGroup where TKey : notnull { /// @@ -19,7 +19,7 @@ public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCo /// /// The key of the group. /// The collection of items to add in the group. - public ReadOnlyObservableGroup(TKey key, ObservableCollection collection) + public ReadOnlyObservableGroup(TKey key, ObservableCollection collection) : base(collection) { Key = key; @@ -29,7 +29,7 @@ public ReadOnlyObservableGroup(TKey key, ObservableCollection collection /// Initializes a new instance of the class. /// /// The to wrap. - public ReadOnlyObservableGroup(ObservableGroup group) + public ReadOnlyObservableGroup(ObservableGroup group) : base(group) { Key = group.Key; From 224b3342e5baf006844ebee9304385d641565e4e Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 19 Mar 2022 19:44:45 +0100 Subject: [PATCH 09/22] Add ILookup<,> to observable grouped collections --- .../ObservableGroupedCollectionExtensions.cs | 2 +- ...ervableGroupedCollection{TKey,TElement}.cs | 24 +++++++++++++++++-- ...ervableGroupedCollection{TKey,TElement}.cs | 23 +++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs index 44a2b436e..b594deb51 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs @@ -69,7 +69,7 @@ static void ThrowArgumentExceptionForKeyNotFound() [MethodImpl(MethodImplOptions.NoInlining)] static ObservableGroup? FirstOrDefaultWithLinq(ObservableGroupedCollection source, TKey key) { - return source.FirstOrDefault(group => EqualityComparer.Default.Equals(group.Key, key)); + return Enumerable.FirstOrDefault>(source, group => EqualityComparer.Default.Equals(group.Key, key)); } return FirstOrDefaultWithLinq(source, key); diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs index 4cfc66dfa..ea83aca33 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// The type of the group keys. /// The type of elements in the collection. -public sealed class ObservableGroupedCollection : ObservableCollection> +public sealed class ObservableGroupedCollection : ObservableCollection>, ILookup where TKey : notnull { /// @@ -30,10 +30,17 @@ public ObservableGroupedCollection() /// /// The initial data to add in the grouped collection. public ObservableGroupedCollection(IEnumerable> collection) - : base(collection.Select(static c => new ObservableGroup(c))) + : base(collection.Select(static group => new ObservableGroup(group))) { } + /// + IEnumerable ILookup.this[TKey key] + { + // TODO: optimize this + get => Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + } + /// /// Tries to get the underlying instance, if present. /// @@ -46,4 +53,17 @@ internal bool TryGetList([NotNullWhen(true)] out List + bool ILookup.Contains(TKey key) + { + // TODO: optimize this + return Enumerable.Any>(this, item => EqualityComparer.Default.Equals(item.Key, key)); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } } diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs index fc7b55860..8ba3e2208 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; @@ -15,7 +16,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// The type of the group keys. /// The type of elements in the collection. -public sealed class ReadOnlyObservableGroupedCollection : ReadOnlyObservableCollection> +public sealed class ReadOnlyObservableGroupedCollection : ReadOnlyObservableCollection>, ILookup where TKey : notnull { /// @@ -37,6 +38,26 @@ public ReadOnlyObservableGroupedCollection(ObservableCollection + IEnumerable ILookup.this[TKey key] + { + // TODO: optimize this + get => Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + } + + /// + bool ILookup.Contains(TKey key) + { + // TODO: optimize this + return Enumerable.Any>(this, item => EqualityComparer.Default.Equals(item.Key, key)); + } + + /// + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetEnumerator(); + } + /// /// Forwards the event whenever it is raised by the wrapped collection. /// From fcdd3561fcebb86c510c53d2e6fe7828a91a5a75 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 19 Mar 2022 19:55:07 +0100 Subject: [PATCH 10/22] Add [DebuggerDisplay] to ReadOnlyObservableGroup<,> --- .../Collections/ReadOnlyObservableGroup{TKey,TElement}.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs index 76dabe718..3befc40c9 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; +using System.Diagnostics; namespace CommunityToolkit.Mvvm.Collections; @@ -11,6 +12,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// /// The type of the group key. /// The type of elements in the group. +[DebuggerDisplay("Key = {Key}, Count = {Count}")] public sealed class ReadOnlyObservableGroup : ReadOnlyObservableCollection, IReadOnlyObservableGroup where TKey : notnull { From 03e85af860be502d219fda233e45e8c35fd8d3f9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 21 Mar 2022 18:05:43 +0100 Subject: [PATCH 11/22] Fix build errors in unit tests --- .../Extensions/Test_EventHandlerExtensions.cs | 2 +- .../Collections/ObservableGroupTests.cs | 28 +- ...ervableGroupedCollectionExtensionsTests.cs | 243 ++++++++++-------- .../ObservableGroupedCollectionTests.cs | 14 +- .../ReadOnlyObservableGroupTests.cs | 44 ++-- ...eadOnlyObservableGroupedCollectionTests.cs | 160 ++++++------ 6 files changed, 265 insertions(+), 226 deletions(-) diff --git a/tests/CommunityToolkit.Common.UnitTests/Extensions/Test_EventHandlerExtensions.cs b/tests/CommunityToolkit.Common.UnitTests/Extensions/Test_EventHandlerExtensions.cs index 643fea3b4..764d2d024 100644 --- a/tests/CommunityToolkit.Common.UnitTests/Extensions/Test_EventHandlerExtensions.cs +++ b/tests/CommunityToolkit.Common.UnitTests/Extensions/Test_EventHandlerExtensions.cs @@ -80,7 +80,7 @@ public void Test_EventHandlerExtensions_UsingDeferralCausesAwait() Assert.IsTrue(handlersTask.IsCompleted); } - [DataTestMethod] + [TestMethod] [DataRow(0, 1)] [DataRow(1, 0)] public void Test_EventHandlerExtensions_MultipleHandlersCauseAwait(int firstToReleaseDeferral, int lastToReleaseDeferral) diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs index f25a8f1df..2022b4459 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs @@ -15,7 +15,7 @@ public class ObservableGroupTests [TestMethod] public void Ctor_ShouldHaveExpectedState() { - ObservableGroup? group = new("key"); + ObservableGroup group = new("key"); Assert.AreEqual(group.Key, "key"); Assert.AreEqual(group.Count, 0); @@ -24,8 +24,8 @@ public void Ctor_ShouldHaveExpectedState() [TestMethod] public void Ctor_WithGrouping_ShouldHaveExpectedState() { - IntGroup? source = new("key", new[] { 1, 2, 3 }); - ObservableGroup? group = new(source); + IntGroup source = new("key", new[] { 1, 2, 3 }); + ObservableGroup group = new(source); Assert.AreEqual(group.Key, "key"); CollectionAssert.AreEqual(group, new[] { 1, 2, 3 }); @@ -34,8 +34,8 @@ public void Ctor_WithGrouping_ShouldHaveExpectedState() [TestMethod] public void Ctor_WithCollection_ShouldHaveExpectedState() { - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? group = new("key", source); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup group = new("key", source); Assert.AreEqual(group.Key, "key"); CollectionAssert.AreEqual(group, new[] { 1, 2, 3 }); @@ -45,8 +45,8 @@ public void Ctor_WithCollection_ShouldHaveExpectedState() public void Add_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? group = new("key", source); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup group = new("key", source); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; group.Add(4); @@ -60,8 +60,8 @@ public void Add_ShouldRaiseEvent() public void Update_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? group = new("key", source); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup group = new("key", source); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; group[1] = 4; @@ -75,7 +75,7 @@ public void Update_ShouldRaiseEvent() public void Remove_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; + int[] source = new[] { 1, 2, 3 }; ObservableGroup? group = new("key", source); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; @@ -90,7 +90,7 @@ public void Remove_ShouldRaiseEvent() public void Clear_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; + int[] source = new[] { 1, 2, 3 }; ObservableGroup? group = new("key", source); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; @@ -101,13 +101,13 @@ public void Clear_ShouldRaiseEvent() Assert.IsTrue(collectionChangedEventRaised); } - [DataTestMethod] + [TestMethod] [DataRow(0)] [DataRow(3)] public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) { - ObservableGroup? group = new("key", Enumerable.Range(0, count)); - IReadOnlyObservableGroup? iReadOnlyObservableGroup = (IReadOnlyObservableGroup)group; + ObservableGroup group = new("key", Enumerable.Range(0, count)); + IReadOnlyObservableGroup iReadOnlyObservableGroup = group; Assert.AreEqual(iReadOnlyObservableGroup.Key, "key"); Assert.AreEqual(iReadOnlyObservableGroup.Count, count); diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index 43aa55afe..bc8a1b62a 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -15,12 +15,15 @@ public class ObservableGroupedCollectionExtensionsTests [TestMethod] public void First_WhenGroupExists_ShouldReturnFirstGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); - ObservableGroup? target = groupedCollection.AddGroup("B", 10); + + ObservableGroup target = groupedCollection.AddGroup("B", 10); + _ = groupedCollection.AddGroup("B", 42); - ObservableGroup? result = groupedCollection.First("B"); + ObservableGroup result = groupedCollection.First("B"); Assert.AreSame(result, target); } @@ -29,7 +32,8 @@ public void First_WhenGroupExists_ShouldReturnFirstGroup() [ExpectedException(typeof(InvalidOperationException))] public void First_WhenGroupDoesNotExist_ShouldThrow() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); _ = groupedCollection.First("I do not exist"); @@ -38,9 +42,12 @@ public void First_WhenGroupDoesNotExist_ShouldThrow() [TestMethod] public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); - ObservableGroup? target = groupedCollection.AddGroup("B", 10); + + ObservableGroup target = groupedCollection.AddGroup("B", 10); + _ = groupedCollection.AddGroup("B", 42); ObservableGroup? result = groupedCollection.FirstOrDefault("B"); @@ -51,7 +58,8 @@ public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup() [TestMethod] public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); ObservableGroup? result = groupedCollection.FirstOrDefault("I do not exist"); @@ -62,7 +70,8 @@ public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() [TestMethod] public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); _ = groupedCollection.AddGroup("B", 10, 11, 12); _ = groupedCollection.AddGroup("B", 42); @@ -72,13 +81,14 @@ public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue Assert.AreEqual(result, 12); } - [DataTestMethod] + [TestMethod] [DataRow(-1)] [DataRow(3)] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int index) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); _ = groupedCollection.AddGroup("B", 10, 11, 12); _ = groupedCollection.AddGroup("B", 42); @@ -90,7 +100,8 @@ public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int in [ExpectedException(typeof(InvalidOperationException))] public void ElementAt_WhenGroupDoesNotExist_ShouldThrow() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); _ = groupedCollection.ElementAt("I do not exist", 0); @@ -99,7 +110,8 @@ public void ElementAt_WhenGroupDoesNotExist_ShouldThrow() [TestMethod] public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); _ = groupedCollection.AddGroup("B", 10, 11, 12); _ = groupedCollection.AddGroup("B", 42); @@ -109,12 +121,13 @@ public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue( Assert.AreEqual(result, 12); } - [DataTestMethod] + [TestMethod] [DataRow(-1)] [DataRow(3)] public void ElementAtOrDefault_WhenGroupExistsAndIndexOutOfRange_ShouldReturnDefaultValue(int index) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); _ = groupedCollection.AddGroup("B", 10, 11, 12); _ = groupedCollection.AddGroup("B", 42); @@ -127,7 +140,8 @@ public void ElementAtOrDefault_WhenGroupExistsAndIndexOutOfRange_ShouldReturnDef [TestMethod] public void ElementAtOrDefault_WhenGroupDoesNotExist_ShouldReturnDefaultValue() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 23); int result = groupedCollection.ElementAtOrDefault("I do not exist", 0); @@ -138,9 +152,9 @@ public void ElementAtOrDefault_WhenGroupDoesNotExist_ShouldReturnDefaultValue() [TestMethod] public void AddGroup_WithItem_ShouldAddGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); - ObservableGroup? addedGroup = groupedCollection.AddGroup("new key", 23); + ObservableGroup addedGroup = groupedCollection.AddGroup("new key", 23); Assert.IsNotNull(addedGroup); Assert.AreEqual(addedGroup.Key, "new key"); @@ -151,9 +165,9 @@ public void AddGroup_WithItem_ShouldAddGroup() [TestMethod] public void AddGroup_WithCollection_ShouldAddGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); - ObservableGroup? addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 }); + ObservableGroup addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 }); Assert.IsNotNull(addedGroup); Assert.AreEqual(addedGroup.Key, "new key"); @@ -164,9 +178,9 @@ public void AddGroup_WithCollection_ShouldAddGroup() [TestMethod] public void AddGroup_WithParamsCollection_ShouldAddGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); - ObservableGroup? addedGroup = groupedCollection.AddGroup("new key", 23, 10, 42); + ObservableGroup addedGroup = groupedCollection.AddGroup("new key", 23, 10, 42); Assert.IsNotNull(addedGroup); Assert.AreEqual(addedGroup.Key, "new key"); @@ -177,9 +191,9 @@ public void AddGroup_WithParamsCollection_ShouldAddGroup() [TestMethod] public void AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); - ObservableGroup? addedGroup = groupedCollection.AddItem("new key", 23); + ObservableGroup addedGroup = groupedCollection.AddItem("new key", 23); Assert.IsNotNull(addedGroup); Assert.AreEqual(addedGroup.Key, "new key"); @@ -190,12 +204,15 @@ public void AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() [TestMethod] public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); - ObservableGroup? targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + + ObservableGroup targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + _ = groupedCollection.AddGroup("C", 7, 8); - ObservableGroup? addedGroup = groupedCollection.AddItem("B", 23); + ObservableGroup addedGroup = groupedCollection.AddItem("B", 23); Assert.AreSame(addedGroup, targetGroup); Assert.AreEqual(addedGroup.Key, "B"); @@ -203,26 +220,29 @@ public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGr Assert.AreEqual(groupedCollection.Count, 3); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 5, 6, 23 }); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6, 23 }); - Assert.AreEqual(groupedCollection.ElementAt(2).Key, "C"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 7, 8 }); + Assert.AreEqual(groupedCollection[2].Key, "C"); + CollectionAssert.AreEqual(groupedCollection[2], new[] { 7, 8 }); } [TestMethod] public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExistingGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); - ObservableGroup? targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + + ObservableGroup targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + _ = groupedCollection.AddGroup("B", 7, 8, 9); _ = groupedCollection.AddGroup("C", 10, 11); - ObservableGroup? addedGroup = groupedCollection.AddItem("B", 23); + ObservableGroup addedGroup = groupedCollection.AddItem("B", 23); Assert.AreSame(addedGroup, targetGroup); Assert.AreEqual(addedGroup.Key, "B"); @@ -230,99 +250,109 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis Assert.AreEqual(groupedCollection.Count, 4); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 5, 6, 23 }); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6, 23 }); - Assert.AreEqual(groupedCollection.ElementAt(2).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 7, 8, 9 }); + Assert.AreEqual(groupedCollection[2].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[2], new[] { 7, 8, 9 }); - Assert.AreEqual(groupedCollection.ElementAt(3).Key, "C"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(3), new[] { 10, 11 }); + Assert.AreEqual(groupedCollection[3].Key, "C"); + CollectionAssert.AreEqual(groupedCollection[3], new[] { 10, 11 }); } [TestMethod] [ExpectedException(typeof(InvalidOperationException))] public void InsertItem_WhenGroupDoesNotExist_ShouldThrow() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.InsertItem("I do not exist", 0, 23); } - [DataTestMethod] + [TestMethod] [DataRow(-1)] [DataRow(4)] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void InsertItem_WhenIndexOutOfRange_ShouldThrow(int index) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.InsertItem("A", index, 23); } - [DataTestMethod] + [TestMethod] [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) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 4, 5); - ObservableGroup? targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); + + ObservableGroup targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); + _ = groupedCollection.AddGroup("B", 6, 7); - ObservableGroup? group = groupedCollection.InsertItem("B", index, 23); + ObservableGroup group = groupedCollection.InsertItem("B", index, 23); Assert.AreSame(group, targetGroup); Assert.AreEqual(groupedCollection.Count, 3); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 4, 5 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 4, 5 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), expecteGroupValues); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], expecteGroupValues); - Assert.AreEqual(groupedCollection.ElementAt(2).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 6, 7 }); + Assert.AreEqual(groupedCollection[2].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[2], new[] { 6, 7 }); } [TestMethod] [ExpectedException(typeof(InvalidOperationException))] public void SetItem_WhenGroupDoesNotExist_ShouldThrow() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.SetItem("I do not exist", 0, 23); } - [DataTestMethod] + [TestMethod] [DataRow(-1)] [DataRow(3)] [ExpectedException(typeof(ArgumentOutOfRangeException))] public void SetItem_WhenIndexOutOfRange_ShouldThrow(int index) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.SetItem("A", index, 23); } - [DataTestMethod] + [TestMethod] [DataRow(0, new[] { 23, 2, 3 })] [DataRow(1, new[] { 1, 23, 3 })] [DataRow(2, new[] { 1, 2, 23 })] public void SetItem_WithValidIndex_WithSeveralGroups_ShouldReplaceItemInFirstGroup(int index, int[] expectedGroupValues) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 4, 5); - ObservableGroup? targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); + + ObservableGroup targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); + _ = groupedCollection.AddGroup("B", 6, 7); ObservableGroup? group = groupedCollection.SetItem("B", index, 23); @@ -331,47 +361,50 @@ public void SetItem_WithValidIndex_WithSeveralGroups_ShouldReplaceItemInFirstGro Assert.AreEqual(groupedCollection.Count, 3); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 4, 5 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 4, 5 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), expectedGroupValues); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], expectedGroupValues); - Assert.AreEqual(groupedCollection.ElementAt(2).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 6, 7 }); + Assert.AreEqual(groupedCollection[2].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[2], new[] { 6, 7 }); } [TestMethod] public void RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); groupedCollection.RemoveGroup("I do not exist"); Assert.AreEqual(groupedCollection.Count, 1); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); } [TestMethod] public void RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.AddGroup("B", 4, 5, 6); groupedCollection.RemoveGroup("B"); Assert.AreEqual(groupedCollection.Count, 1); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); } [TestMethod] public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.AddGroup("B", 4, 5, 6); _ = groupedCollection.AddGroup("B", 7, 8); @@ -380,19 +413,20 @@ public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() Assert.AreEqual(groupedCollection.Count, 2); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 7, 8 }); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], new[] { 7, 8 }); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.AddGroup("B", 4, 5, 6); @@ -400,19 +434,20 @@ public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfE Assert.AreEqual(groupedCollection.Count, 2); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 5, 6 }); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6 }); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.AddGroup("B", 4, 5, 6); @@ -420,19 +455,20 @@ public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool r Assert.AreEqual(groupedCollection.Count, 2); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 5, 6 }); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6 }); } - [DataTestMethod] + [TestMethod] [DataRow(true)] [DataRow(false)] public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool removeGroupIfEmpty) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.AddGroup("B", 4, 5, 6); @@ -440,19 +476,20 @@ public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool remo Assert.AreEqual(groupedCollection.Count, 2); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 6 }); + Assert.AreEqual(groupedCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 6 }); } - [DataTestMethod] + [TestMethod] [DataRow(true, true)] [DataRow(false, false)] public void RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool removeGroupIfEmpty, bool expectGroupRemoved) { - ObservableGroupedCollection? groupedCollection = new(); + ObservableGroupedCollection groupedCollection = new(); + _ = groupedCollection.AddGroup("A", 1, 2, 3); _ = groupedCollection.AddGroup("B", 4); @@ -460,13 +497,13 @@ public void RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool rem Assert.AreEqual(groupedCollection.Count, expectGroupRemoved ? 1 : 2); - Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 }); + Assert.AreEqual(groupedCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 }); if (!expectGroupRemoved) { - Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B"); - Assert.AreEqual(groupedCollection.ElementAt(1).Count, 0); + Assert.AreEqual(groupedCollection[1].Key, "B"); + Assert.AreEqual(groupedCollection[1].Count, 0); } } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs index 9df27d94e..1a845905f 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs @@ -15,7 +15,7 @@ public class ObservableGroupedCollectionTests [TestMethod] public void Ctor_ShouldHaveExpectedValues() { - ObservableGroupedCollection? groupCollection = new(); + ObservableGroupedCollection groupCollection = new(); Assert.AreEqual(groupCollection.Count, 0); } @@ -23,19 +23,19 @@ public void Ctor_ShouldHaveExpectedValues() [TestMethod] public void Ctor_WithGroups_ShouldHaveExpectedValues() { - List>? groups = new() + List> groups = new() { new IntGroup("A", new[] { 1, 3, 5 }), new IntGroup("B", new[] { 2, 4, 6 }), }; - ObservableGroupedCollection? groupCollection = new(groups); + ObservableGroupedCollection groupCollection = new(groups); Assert.AreEqual(groupCollection.Count, 2); - Assert.AreEqual(groupCollection.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(groupCollection.ElementAt(0), new[] { 1, 3, 5 }); + Assert.AreEqual(groupCollection[0].Key, "A"); + CollectionAssert.AreEqual(groupCollection[0], new[] { 1, 3, 5 }); - Assert.AreEqual(groupCollection.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(groupCollection.ElementAt(1), new[] { 2, 4, 6 }); + Assert.AreEqual(groupCollection[1].Key, "B"); + CollectionAssert.AreEqual(groupCollection[1], new[] { 2, 4, 6 }); } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs index 3e08f299b..b6074102e 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs @@ -17,8 +17,8 @@ public class ReadOnlyObservableGroupTests [TestMethod] public void Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState() { - ObservableCollection? source = new(new[] { 1, 2, 3 }); - ReadOnlyObservableGroup? group = new("key", source); + ObservableCollection source = new(new[] { 1, 2, 3 }); + ReadOnlyObservableGroup group = new("key", source); Assert.AreEqual(group.Key, "key"); CollectionAssert.AreEqual(group, new[] { 1, 2, 3 }); @@ -27,9 +27,9 @@ public void Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState() [TestMethod] public void Ctor_ObservableGroup_ShouldHaveExpectedInitialState() { - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? sourceGroup = new("key", source); - ReadOnlyObservableGroup? group = new(sourceGroup); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup sourceGroup = new("key", source); + ReadOnlyObservableGroup group = new(sourceGroup); Assert.AreEqual(group.Key, "key"); CollectionAssert.AreEqual(group, new[] { 1, 2, 3 }); @@ -39,7 +39,7 @@ public void Ctor_ObservableGroup_ShouldHaveExpectedInitialState() public void Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState() { ObservableCollection source = new() { 1, 2, 3 }; - ReadOnlyObservableGroup? group = new("key", source); + ReadOnlyObservableGroup group = new("key", source); Assert.AreEqual(group.Key, "key"); CollectionAssert.AreEqual(group, new[] { 1, 2, 3 }); @@ -49,9 +49,9 @@ public void Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState() public void Add_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? sourceGroup = new("key", source); - ReadOnlyObservableGroup? group = new(sourceGroup); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup sourceGroup = new("key", source); + ReadOnlyObservableGroup group = new(sourceGroup); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; sourceGroup.Add(4); @@ -66,9 +66,9 @@ public void Add_ShouldRaiseEvent() public void Update_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? sourceGroup = new("key", source); - ReadOnlyObservableGroup? group = new(sourceGroup); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup sourceGroup = new("key", source); + ReadOnlyObservableGroup group = new(sourceGroup); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; sourceGroup[1] = 4; @@ -83,9 +83,9 @@ public void Update_ShouldRaiseEvent() public void Remove_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? sourceGroup = new("key", source); - ReadOnlyObservableGroup? group = new(sourceGroup); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup sourceGroup = new("key", source); + ReadOnlyObservableGroup group = new(sourceGroup); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; _ = sourceGroup.Remove(1); @@ -100,9 +100,9 @@ public void Remove_ShouldRaiseEvent() public void Clear_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; - int[]? source = new[] { 1, 2, 3 }; - ObservableGroup? sourceGroup = new("key", source); - ReadOnlyObservableGroup? group = new(sourceGroup); + int[] source = new[] { 1, 2, 3 }; + ObservableGroup sourceGroup = new("key", source); + ReadOnlyObservableGroup group = new(sourceGroup); ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; sourceGroup.Clear(); @@ -113,14 +113,14 @@ public void Clear_ShouldRaiseEvent() Assert.IsTrue(collectionChangedEventRaised); } - [DataTestMethod] + [TestMethod] [DataRow(0)] [DataRow(3)] public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) { - ObservableGroup? sourceGroup = new("key", Enumerable.Range(0, count)); - ReadOnlyObservableGroup? group = new(sourceGroup); - IReadOnlyObservableGroup? iReadOnlyObservableGroup = (IReadOnlyObservableGroup)group; + ObservableGroup sourceGroup = new("key", Enumerable.Range(0, count)); + ReadOnlyObservableGroup group = new(sourceGroup); + IReadOnlyObservableGroup iReadOnlyObservableGroup = group; Assert.AreEqual(iReadOnlyObservableGroup.Key, "key"); Assert.AreEqual(iReadOnlyObservableGroup.Count, count); diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs index d105fd1d6..b15c66673 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs @@ -20,8 +20,8 @@ public class ReadOnlyObservableGroupedCollectionTests [TestMethod] public void Ctor_WithEmptySource_ShoudInitializeObject() { - ObservableGroupedCollection? source = new(); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ObservableGroupedCollection source = new(); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); Assert.AreEqual(readOnlyGroup.Count, 0); CollectionAssert.AreEqual(readOnlyGroup, Array.Empty()); @@ -30,62 +30,62 @@ public void Ctor_WithEmptySource_ShoudInitializeObject() [TestMethod] public void Ctor_WithObservableGroupedCollection_ShoudInitializeObject() { - List>? groups = new() + List> groups = new() { new IntGroup("A", new[] { 1, 3, 5 }), new IntGroup("B", new[] { 2, 4, 6 }), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); Assert.AreEqual(readOnlyGroup.Count, 2); - Assert.AreEqual(readOnlyGroup.ElementAt(0).Key, "A"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(0), new[] { 1, 3, 5 }); + Assert.AreEqual(readOnlyGroup[0].Key, "A"); + CollectionAssert.AreEquivalent(readOnlyGroup[0], new[] { 1, 3, 5 }); - Assert.AreEqual(readOnlyGroup.ElementAt(1).Key, "B"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(1), new[] { 2, 4, 6 }); + Assert.AreEqual(readOnlyGroup[1].Key, "B"); + CollectionAssert.AreEquivalent(readOnlyGroup[1], new[] { 2, 4, 6 }); } [TestMethod] public void Ctor_WithListOfReadOnlyObservableGroupSource_ShoudInitializeObject() { - ObservableCollection>? source = new() + ObservableCollection> source = new() { new ReadOnlyObservableGroup("A", new ObservableCollection { 1, 3, 5 }), new ReadOnlyObservableGroup("B", new ObservableCollection { 2, 4, 6 }), }; - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); Assert.AreEqual(readOnlyGroup.Count, 2); - Assert.AreEqual(readOnlyGroup.ElementAt(0).Key, "A"); - CollectionAssert.AreEqual(readOnlyGroup.ElementAt(0), new[] { 1, 3, 5 }); + Assert.AreEqual(readOnlyGroup[0].Key, "A"); + CollectionAssert.AreEqual(readOnlyGroup[0], new[] { 1, 3, 5 }); - Assert.AreEqual(readOnlyGroup.ElementAt(1).Key, "B"); - CollectionAssert.AreEqual(readOnlyGroup.ElementAt(1), new[] { 2, 4, 6 }); + Assert.AreEqual(readOnlyGroup[1].Key, "B"); + CollectionAssert.AreEqual(readOnlyGroup[1], new[] { 2, 4, 6 }); } [TestMethod] public void IListImplementation_Properties_ShoudReturnExpectedValues() { - List>? groups = new() + List> groups = new() { new IntGroup("A", new[] { 1, 3, 5 }), new IntGroup("B", new[] { 2, 4, 6 }), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); - IList? list = (IList)readOnlyGroup; + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); + IList list = readOnlyGroup; Assert.AreEqual(list.Count, 2); - ReadOnlyObservableGroup? group0 = (ReadOnlyObservableGroup)list[0]!; + ReadOnlyObservableGroup group0 = (ReadOnlyObservableGroup)list[0]!; Assert.AreEqual(group0.Key, "A"); CollectionAssert.AreEqual(group0, new[] { 1, 3, 5 }); - ReadOnlyObservableGroup? group1 = (ReadOnlyObservableGroup)list[1]!; + ReadOnlyObservableGroup group1 = (ReadOnlyObservableGroup)list[1]!; Assert.AreEqual(group1.Key, "B"); CollectionAssert.AreEqual(group1, new[] { 2, 4, 6 }); @@ -99,14 +99,14 @@ public void IListImplementation_Properties_ShoudReturnExpectedValues() [TestMethod] public void IListImplementation_MutableMethods_ShoudThrow() { - List>? groups = new() + List> groups = new() { new IntGroup("A", new[] { 1, 3, 5 }), new IntGroup("B", new[] { 2, 4, 6 }), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); - IList? list = (IList)readOnlyGroup; + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); + IList list = readOnlyGroup; ReadOnlyObservableGroup? testGroup = new("test", new ObservableCollection()); @@ -123,22 +123,22 @@ public void IListImplementation_MutableMethods_ShoudThrow() list.CopyTo(array, 0); } - [DataTestMethod] + [TestMethod] [DataRow(-1)] [DataRow(0)] [DataRow(1)] [DataRow(2)] public void IListImplementation_IndexOf_ShoudReturnExpectedValue(int groupIndex) { - List>? groups = new() + List> groups = new() { new IntGroup("A", new[] { 1, 3, 5 }), new IntGroup("B", new[] { 2, 4, 6 }), new IntGroup("C", new[] { 7, 8, 9 }), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); - IList? list = (IList)readOnlyGroup; + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); + IList list = readOnlyGroup; object? groupToSearch = groupIndex >= 0 ? list[groupIndex] : null; @@ -147,20 +147,20 @@ public void IListImplementation_IndexOf_ShoudReturnExpectedValue(int groupIndex) Assert.AreEqual(index, groupIndex); } - [DataTestMethod] + [TestMethod] [DataRow(-1, false)] [DataRow(0, true)] [DataRow(1, true)] public void IListImplementation_Contains_ShoudReturnExpectedValue(int groupIndex, bool expectedResult) { - List>? groups = new() + List> groups = new() { new IntGroup("A", new[] { 1, 3, 5 }), new IntGroup("B", new[] { 2, 4, 6 }), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); - IList? list = (IList)readOnlyGroup; + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); + IList list = readOnlyGroup; object? groupToSearch = groupIndex >= 0 ? list[groupIndex] : null; @@ -169,7 +169,7 @@ public void IListImplementation_Contains_ShoudReturnExpectedValue(int groupIndex Assert.AreEqual(result, expectedResult); } - [DataTestMethod] + [TestMethod] [DataRow(0, 0)] [DataRow(3, 3)] public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int expectedInsertionIndex) @@ -177,14 +177,14 @@ public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int exp NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; - int[]? itemsList = new[] { 1, 2, 3 }; - ObservableGroupedCollection? source = new(); + int[] itemsList = new[] { 1, 2, 3 }; + ObservableGroupedCollection source = new(); for (int i = 0; i < sourceInitialItemsCount; i++) { source.Add(new ObservableGroup($"group {i}", Enumerable.Empty())); } - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); ((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) => { collectionChangedEventArgs = e; @@ -198,8 +198,9 @@ public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int exp Assert.AreEqual(readOnlyGroup.Count, expectedReadOnlyGroupCount); - Assert.AreEqual(readOnlyGroup.Last().Key, "Add"); - CollectionAssert.AreEquivalent(readOnlyGroup.Last(), itemsList); + // TODO + //Assert.AreEqual(readOnlyGroup.Last().Key, "Add"); + //CollectionAssert.AreEquivalent(readOnlyGroup.Last(), itemsList); Assert.IsTrue(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); @@ -210,7 +211,7 @@ public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int exp Assert.IsTrue(isAddEventValid); } - [DataTestMethod] + [TestMethod] [DataRow(0)] [DataRow(1)] [DataRow(2)] @@ -219,13 +220,13 @@ public void InsertGroupInSource_ShouldAddGroup(int insertionIndex) NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; - int[]? itemsList = new[] { 1, 2, 3 }; - ObservableGroupedCollection? source = new() + int[] itemsList = new[] { 1, 2, 3 }; + ObservableGroupedCollection source = new() { new ObservableGroup("Group0", new[] { 10, 20, 30 }), new ObservableGroup("Group1", new[] { 40, 50, 60 }) }; - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); ((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) => { collectionChangedEventArgs = e; @@ -237,8 +238,9 @@ public void InsertGroupInSource_ShouldAddGroup(int insertionIndex) Assert.AreEqual(readOnlyGroup.Count, 3); - Assert.AreEqual(readOnlyGroup.ElementAt(insertionIndex).Key, "Add"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(insertionIndex), itemsList); + // TODO + //Assert.AreEqual(readOnlyGroup.ElementAt(insertionIndex).Key, "Add"); + //CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(insertionIndex), itemsList); Assert.IsTrue(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); @@ -255,15 +257,15 @@ public void RemoveGroupInSource_ShoudRemoveGroup() NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; - int[]? aItemsList = new[] { 1, 2, 3 }; - int[]? bItemsList = new[] { 2, 4, 6 }; - List>? groups = new() + int[] aItemsList = new[] { 1, 2, 3 }; + int[] bItemsList = new[] { 2, 4, 6 }; + List> groups = new() { new IntGroup("A", aItemsList), new IntGroup("B", bItemsList), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); ((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) => { collectionChangedEventArgs = e; @@ -275,8 +277,8 @@ public void RemoveGroupInSource_ShoudRemoveGroup() Assert.AreEqual(readOnlyGroup.Count, 1); - Assert.AreEqual(readOnlyGroup.ElementAt(0).Key, "A"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(0), aItemsList); + Assert.AreEqual(readOnlyGroup[0].Key, "A"); + CollectionAssert.AreEquivalent(readOnlyGroup[0], aItemsList); Assert.IsTrue(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); @@ -287,7 +289,7 @@ public void RemoveGroupInSource_ShoudRemoveGroup() Assert.IsTrue(isRemoveEventValid); } - [DataTestMethod] + [TestMethod] [DataRow(1, 0)] [DataRow(0, 1)] public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) @@ -295,15 +297,15 @@ public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; - int[]? aItemsList = new[] { 1, 2, 3 }; - int[]? bItemsList = new[] { 2, 4, 6 }; - List>? groups = new() + int[] aItemsList = new[] { 1, 2, 3 }; + int[] bItemsList = new[] { 2, 4, 6 }; + List> groups = new() { new IntGroup("A", aItemsList), new IntGroup("B", bItemsList), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); ((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) => { collectionChangedEventArgs = e; @@ -315,11 +317,11 @@ public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) Assert.AreEqual(readOnlyGroup.Count, 2); - Assert.AreEqual(readOnlyGroup.ElementAt(0).Key, "B"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(0), bItemsList); + Assert.AreEqual(readOnlyGroup[0].Key, "B"); + CollectionAssert.AreEquivalent(readOnlyGroup[0], bItemsList); - Assert.AreEqual(readOnlyGroup.ElementAt(1).Key, "A"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(1), aItemsList); + Assert.AreEqual(readOnlyGroup[1].Key, "A"); + CollectionAssert.AreEquivalent(readOnlyGroup[1], aItemsList); Assert.IsFalse(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); @@ -336,15 +338,15 @@ public void ClearSource_ShoudClear() NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; - int[]? aItemsList = new[] { 1, 2, 3 }; - int[]? bItemsList = new[] { 2, 4, 6 }; - List>? groups = new() + int[] aItemsList = new[] { 1, 2, 3 }; + int[] bItemsList = new[] { 2, 4, 6 }; + List> groups = new() { new IntGroup("A", aItemsList), new IntGroup("B", bItemsList), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); ((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) => { collectionChangedEventArgs = e; @@ -371,16 +373,16 @@ public void ReplaceGroupInSource_ShoudReplaceGroup() NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; - int[]? aItemsList = new[] { 1, 2, 3 }; - int[]? bItemsList = new[] { 2, 4, 6 }; - int[]? cItemsList = new[] { 7, 8, 9 }; - List>? groups = new() + int[] aItemsList = new[] { 1, 2, 3 }; + int[] bItemsList = new[] { 2, 4, 6 }; + int[] cItemsList = new[] { 7, 8, 9 }; + List> groups = new() { new IntGroup("A", aItemsList), new IntGroup("B", bItemsList), }; - ObservableGroupedCollection? source = new(groups); - ReadOnlyObservableGroupedCollection? readOnlyGroup = new(source); + ObservableGroupedCollection source = new(groups); + ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); ((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) => { collectionChangedEventArgs = e; @@ -392,11 +394,11 @@ public void ReplaceGroupInSource_ShoudReplaceGroup() Assert.AreEqual(readOnlyGroup.Count, 2); - Assert.AreEqual(readOnlyGroup.ElementAt(0).Key, "C"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(0), cItemsList); + Assert.AreEqual(readOnlyGroup[0].Key, "C"); + CollectionAssert.AreEquivalent(readOnlyGroup[0], cItemsList); - Assert.AreEqual(readOnlyGroup.ElementAt(1).Key, "B"); - CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(1), bItemsList); + Assert.AreEqual(readOnlyGroup[1].Key, "B"); + CollectionAssert.AreEquivalent(readOnlyGroup[1], bItemsList); Assert.IsFalse(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); From 2712e9b282ca610f78ebb43be73e8761f0993b40 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Fri, 25 Mar 2022 15:38:31 +0100 Subject: [PATCH 12/22] Seal ObservableGroup for consistency --- .../Collections/ObservableGroup{TKey,TElement}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs index 448dc47ca..79165cc59 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.Mvvm.Collections; /// The type of the group key. /// The type of elements in the group. [DebuggerDisplay("Key = {Key}, Count = {Count}")] -public class ObservableGroup : ObservableCollection, IReadOnlyObservableGroup +public sealed class ObservableGroup : ObservableCollection, IReadOnlyObservableGroup where TKey : notnull { /// From 08fca92424d11adaf0470d7a0e408b911f9820ef Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 30 Mar 2022 14:49:05 +0200 Subject: [PATCH 13/22] Update ObservableGroupedCollectionExtensions with new shape --- .../ObservableGroupedCollectionExtensions.cs | 709 +++++++++++++----- .../ObservableGroup{TKey,TElement}.cs | 15 + ...ervableGroupedCollectionExtensionsTests.cs | 234 ++---- 3 files changed, 572 insertions(+), 386 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs index b594deb51..31793b3fd 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs @@ -4,13 +4,15 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; namespace CommunityToolkit.Mvvm.Collections; /// -/// The extensions methods to simplify the usage of . +/// The extensions methods to simplify the usage of . /// public static class ObservableGroupedCollectionExtensions { @@ -18,18 +20,19 @@ 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 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) + public static ObservableGroup FirstGroupByKey(this ObservableGroupedCollection source, TKey key) where TKey : notnull { - ObservableGroup? group = source.FirstOrDefault(key); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); if (group is null) { + [DoesNotReturn] static void ThrowArgumentExceptionForKeyNotFound() { throw new InvalidOperationException("The requested key was not present in the collection."); @@ -38,23 +41,23 @@ static void ThrowArgumentExceptionForKeyNotFound() ThrowArgumentExceptionForKeyNotFound(); } - return group!; + return group; } /// /// 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 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) + public static ObservableGroup? FirstGroupByKeyOrDefault(this ObservableGroupedCollection source, TKey key) where TKey : notnull { - if (source.TryGetList(out List>? list)) + if (source.TryGetList(out List>? list)) { - foreach (ObservableGroup? group in list) + foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { @@ -65,111 +68,415 @@ static void ThrowArgumentExceptionForKeyNotFound() return null; } - // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] - static ObservableGroup? FirstOrDefaultWithLinq(ObservableGroupedCollection source, TKey key) + static ObservableGroup? FirstOrDefaultFallback(ObservableGroupedCollection source, TKey key) { - return Enumerable.FirstOrDefault>(source, group => EqualityComparer.Default.Equals(group.Key, key)); + return Enumerable.FirstOrDefault>(source, group => EqualityComparer.Default.Equals(group.Key, key)); } - return FirstOrDefaultWithLinq(source, key); + return FirstOrDefaultFallback(source, key); } /// - /// Return the element at position from the first group with key. + /// 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 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) + /// The type of the items in the collection. + /// The source instance. + /// The key of the group to add. + /// The added . + public static ObservableGroup AddGroup(this ObservableGroupedCollection source, TKey key) where TKey : notnull - => source.First(key)[index]; + { + ObservableGroup group = new(key); + + source.Add(group); + + return group; + } /// - /// Return the element at position from the first group with key. + /// 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 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) + /// The type of the items in the collection. + /// The source instance. + /// The group of items to add. + /// The added . + public static ObservableGroup AddGroup(this ObservableGroupedCollection source, IGrouping grouping) + where TKey : notnull + { + ObservableGroup group = new(grouping); + + source.Add(group); + + return group; + } + + /// + /// 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) + where TKey : notnull + { + ObservableGroup group = new(key, collection); + + source.Add(group); + + return group; + } + + /// + /// 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 to add. + /// The added . + public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key) where TKey : notnull { - ObservableGroup? group = source.FirstOrDefault(key); + if (source.TryGetList(out List>? list)) + { + int index = 0; + + foreach (ObservableGroup group in list) + { + if (Comparer.Default.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key); + + source.Insert(index, newGroup); - if (group is null || - (uint)index >= (uint)group.Count) + return newGroup; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ObservableGroup InsertGroupFallback(ObservableGroupedCollection source, TKey key) { - return default; + int index = 0; + + foreach (ObservableGroup group in source) + { + if (Comparer.Default.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key); + + source.Insert(index, newGroup); + + return newGroup; } - return group[index]; + return InsertGroupFallback(source, key); } /// - /// Adds a key-value item into a target . + /// 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 type of the items in the collection. + /// The source instance. + /// The group of items to add. /// The added . - public static ObservableGroup AddGroup( - this ObservableGroupedCollection source, - TKey key, - TValue value) + public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, IGrouping grouping) where TKey : notnull - => AddGroup(source, key, new[] { value }); + { + if (source.TryGetList(out List>? list)) + { + int index = 0; + + foreach (ObservableGroup group in list) + { + if (Comparer.Default.Compare(grouping.Key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(grouping); + + source.Insert(index, newGroup); + + return newGroup; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ObservableGroup InsertGroupFallback(ObservableGroupedCollection source, IGrouping grouping) + { + int index = 0; + + foreach (ObservableGroup group in source) + { + if (Comparer.Default.Compare(grouping.Key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(grouping); + + source.Insert(index, newGroup); + + return newGroup; + } + + return InsertGroupFallback(source, grouping); + } /// - /// Adds a key-collection item into a target . + /// 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 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) + public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key, IEnumerable collection) where TKey : notnull - => source.AddGroup(key, (IEnumerable)collection); + { + if (source.TryGetList(out List>? list)) + { + int index = 0; + + foreach (ObservableGroup group in list) + { + if (Comparer.Default.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key, collection); + + source.Insert(index, newGroup); + + return newGroup; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ObservableGroup InsertGroupFallback(ObservableGroupedCollection source, TKey key, IEnumerable collection) + { + int index = 0; + + foreach (ObservableGroup group in source) + { + if (Comparer.Default.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key, collection); + + source.Insert(index, newGroup); + + return newGroup; + } + + return InsertGroupFallback(source, key, collection); + } /// - /// Adds a key-collection item into a target . + /// 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 type of the items in the collection. + /// The source instance. + /// The key of the group to add. + /// The instance to insert at the right position. + /// The added . + public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key, IComparer comparer) + where TKey : notnull + { + if (source.TryGetList(out List>? list)) + { + int index = 0; + + foreach (ObservableGroup group in list) + { + if (comparer.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key); + + source.Insert(index, newGroup); + + return newGroup; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ObservableGroup InsertGroupFallback(ObservableGroupedCollection source, TKey key, IComparer comparer) + { + int index = 0; + + foreach (ObservableGroup group in source) + { + if (comparer.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key); + + source.Insert(index, newGroup); + + return newGroup; + } + + return InsertGroupFallback(source, key, comparer); + } + + /// + /// 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 group of items to add. + /// The instance to insert at the right position. + /// The added . + public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, IGrouping grouping, IComparer comparer) + where TKey : notnull + { + if (source.TryGetList(out List>? list)) + { + int index = 0; + + foreach (ObservableGroup group in list) + { + if (comparer.Compare(grouping.Key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(grouping); + + source.Insert(index, newGroup); + + return newGroup; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ObservableGroup InsertGroupFallback(ObservableGroupedCollection source, IGrouping grouping, IComparer comparer) + { + int index = 0; + + foreach (ObservableGroup group in source) + { + if (comparer.Compare(grouping.Key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(grouping); + + source.Insert(index, newGroup); + + return newGroup; + } + + return InsertGroupFallback(source, grouping, comparer); + } + + /// + /// 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 instance to insert at the right position. /// The collection to add. /// The added . - public static ObservableGroup AddGroup( - this ObservableGroupedCollection source, - TKey key, - IEnumerable collection) + public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key, IComparer comparer, IEnumerable collection) where TKey : notnull { - ObservableGroup? group = new(key, collection); - source.Add(group); + if (source.TryGetList(out List>? list)) + { + int index = 0; - return group; + foreach (ObservableGroup group in list) + { + if (comparer.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key, collection); + + source.Insert(index, newGroup); + + return newGroup; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ObservableGroup InsertGroupFallback(ObservableGroupedCollection source, TKey key, IComparer comparer, IEnumerable collection) + { + int index = 0; + + foreach (ObservableGroup group in source) + { + if (comparer.Compare(key, group.Key) < 0) + { + break; + } + + index++; + } + + ObservableGroup newGroup = new(key, collection); + + source.Insert(index, newGroup); + + return newGroup; + } + + return InsertGroupFallback(source, key, comparer, collection); } /// @@ -177,78 +484,157 @@ public static ObservableGroup AddGroup( /// 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 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) + /// 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, TElement item) where TKey : notnull { - ObservableGroup? group = source.FirstOrDefault(key); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); if (group is null) { - group = new ObservableGroup(key); + group = new ObservableGroup(key) { item }; + source.Add(group); } - - group.Add(item); + else + { + group.Add(item); + } return group; } /// - /// Insert into the first group with key at . + /// Insert into the first group with key. /// /// The type of the group key. - /// The type of the items in the collection. - /// The source instance. + /// 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) + /// The instance of the which will receive the value. + public static ObservableGroup InsertItem(this ObservableGroupedCollection source, TKey key, TElement item) where TKey : notnull { - ObservableGroup? existingGroup = source.First(key); - existingGroup.Insert(index, item); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); + + if (group is null) + { + group = source.InsertGroup(key, new[] { item }); + } + else + { + if (group.TryGetList(out List? list)) + { + int index = 0; + + foreach (TElement element in list) + { + if (Comparer.Default.Compare(item, element) < 0) + { + break; + } + + index++; + } + + group.Insert(index, item); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void InsertItemFallback(ObservableCollection source, TElement item) + { + int index = 0; + + foreach (TElement element in source) + { + if (Comparer.Default.Compare(item, element) < 0) + { + break; + } - return existingGroup; + index++; + } + + source.Insert(index, item); + } + + InsertItemFallback(group, item); + } + + return group; } /// - /// Replace the element at with in the first group with key. + /// Insert into 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 type of the items in the collection. + /// The source instance. + /// The key of the group where to insert . + /// The instance to compare keys. /// 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, + /// The instance to compare elements. + /// The instance of the which will receive the value. + public static ObservableGroup InsertItem( + this ObservableGroupedCollection source, TKey key, - int index, - TValue item) + IComparer keyComparer, + TElement item, + IComparer itemComparer) where TKey : notnull { - ObservableGroup? existingGroup = source.First(key); - existingGroup[index] = item; + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); + + if (group is null) + { + group = source.InsertGroup(key, keyComparer, new[] { item }); + } + else + { + if (group.TryGetList(out List? list)) + { + int index = 0; + + foreach (TElement element in list) + { + if (itemComparer.Compare(item, element) < 0) + { + break; + } + + index++; + } + + group.Insert(index, item); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void InsertItemFallback(ObservableCollection source, TElement item, IComparer comparer) + { + int index = 0; + + foreach (TElement element in source) + { + if (comparer.Compare(item, element) < 0) + { + break; + } - return existingGroup; + index++; + } + + source.Insert(index, item); + } + + InsertItemFallback(group, item, itemComparer); + } + + return group; } /// @@ -259,14 +645,13 @@ public static ObservableGroup SetItem( /// 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) + public static void RemoveGroup(this ObservableGroupedCollection source, TKey key) where TKey : notnull { if (source.TryGetList(out List>? list)) { int index = 0; + foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) @@ -281,11 +666,11 @@ public static void RemoveGroup( } else { - // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] - static void RemoveGroupWithLinq(ObservableGroupedCollection source, TKey key) + static void RemoveGroupFallback(ObservableGroupedCollection source, TKey key) { int index = 0; + foreach (ObservableGroup? group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) @@ -298,7 +683,7 @@ static void RemoveGroupWithLinq(ObservableGroupedCollection source } } - RemoveGroupWithLinq(source, key); + RemoveGroupFallback(source, key); } } @@ -312,16 +697,13 @@ static void RemoveGroupWithLinq(ObservableGroupedCollection source /// 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) + public static void RemoveItem(this ObservableGroupedCollection source, TKey key, TValue item, bool removeGroupIfEmpty = true) where TKey : notnull { if (source.TryGetList(out List>? list)) { int index = 0; + foreach (ObservableGroup? group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) @@ -341,15 +723,11 @@ public static void RemoveItem( } else { - // Fallback method [MethodImpl(MethodImplOptions.NoInlining)] - static void RemoveItemWithLinq( - ObservableGroupedCollection source, - TKey key, - TValue item, - bool removeGroupIfEmpty) + static void RemoveItemFallback(ObservableGroupedCollection source, TKey key, TValue item, bool removeGroupIfEmpty) { int index = 0; + foreach (ObservableGroup? group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) @@ -368,78 +746,7 @@ static void RemoveItemWithLinq( } } - RemoveItemWithLinq(source, key, item, removeGroupIfEmpty); - } - } - - /// - /// 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. - /// is less than zero or is greater than the group elements' count. - public static void RemoveItemAt( - this ObservableGroupedCollection source, - TKey key, - int index, - bool removeGroupIfEmpty = true) - where TKey : notnull - { - if (source.TryGetList(out List>? list)) - { - int groupIndex = 0; - foreach (ObservableGroup? group in list) - { - if (EqualityComparer.Default.Equals(group.Key, key)) - { - group.RemoveAt(index); - - if (removeGroupIfEmpty && group.Count == 0) - { - source.RemoveAt(groupIndex); - } - - return; - } - - groupIndex++; - } - } - else - { - // Fallback method - [MethodImpl(MethodImplOptions.NoInlining)] - static void RemoveItemAtWithLinq( - ObservableGroupedCollection source, - TKey key, - int index, - bool removeGroupIfEmpty) - { - int groupIndex = 0; - foreach (ObservableGroup? group in source) - { - if (EqualityComparer.Default.Equals(group.Key, key)) - { - group.RemoveAt(index); - - if (removeGroupIfEmpty && group.Count == 0) - { - source.RemoveAt(groupIndex); - } - - return; - } - - groupIndex++; - } - } - - RemoveItemAtWithLinq(source, key, index, removeGroupIfEmpty); + RemoveItemFallback(source, key, item, removeGroupIfEmpty); } } } diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs index 79165cc59..9b2cce3b6 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using CommunityToolkit.Mvvm.Collections.Internals; namespace CommunityToolkit.Mvvm.Collections; @@ -69,6 +71,19 @@ public TKey Key } } + /// + /// Tries to get the underlying instance, if present. + /// + /// The resulting , if one was in use. + /// Whether or not a instance has been found. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool TryGetList([NotNullWhen(true)] out List? list) + { + list = Items as List; + + return list is not null; + } + /// object IReadOnlyObservableGroup.Key => Key; diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs index bc8a1b62a..effd5f214 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs @@ -17,13 +17,13 @@ public void First_WhenGroupExists_ShouldReturnFirstGroup() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); + _ = groupedCollection.AddGroup("A", new[] { 23 }); - ObservableGroup target = groupedCollection.AddGroup("B", 10); + ObservableGroup target = groupedCollection.AddGroup("B", new[] { 10 }); - _ = groupedCollection.AddGroup("B", 42); + _ = groupedCollection.AddGroup("B", new[] { 42 }); - ObservableGroup result = groupedCollection.First("B"); + ObservableGroup result = groupedCollection.FirstGroupByKey("B"); Assert.AreSame(result, target); } @@ -34,9 +34,9 @@ public void First_WhenGroupDoesNotExist_ShouldThrow() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); + _ = groupedCollection.AddGroup("A", new[] { 23 }); - _ = groupedCollection.First("I do not exist"); + _ = groupedCollection.FirstGroupByKey("I do not exist"); } [TestMethod] @@ -44,13 +44,13 @@ public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); + _ = groupedCollection.AddGroup("A", new[] { 23 }); - ObservableGroup target = groupedCollection.AddGroup("B", 10); + ObservableGroup target = groupedCollection.AddGroup("B", new[] { 10 }); - _ = groupedCollection.AddGroup("B", 42); + _ = groupedCollection.AddGroup("B", new[] { 42 }); - ObservableGroup? result = groupedCollection.FirstOrDefault("B"); + ObservableGroup? result = groupedCollection.FirstGroupByKeyOrDefault("B"); Assert.AreSame(result, target); } @@ -60,9 +60,9 @@ public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); + _ = groupedCollection.AddGroup("A", new[] { 23 }); - ObservableGroup? result = groupedCollection.FirstOrDefault("I do not exist"); + ObservableGroup? result = groupedCollection.FirstGroupByKeyOrDefault("I do not exist"); Assert.IsNull(result); } @@ -72,11 +72,11 @@ public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); - _ = groupedCollection.AddGroup("B", 10, 11, 12); - _ = groupedCollection.AddGroup("B", 42); + _ = groupedCollection.AddGroup("A", new[] { 23 }); + _ = groupedCollection.AddGroup("B", new[] { 10, 11, 12 }); + _ = groupedCollection.AddGroup("B", new[] { 42 }); - int result = groupedCollection.ElementAt("B", 2); + int result = groupedCollection.FirstGroupByKey("B")[2]; Assert.AreEqual(result, 12); } @@ -89,11 +89,11 @@ public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int in { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); - _ = groupedCollection.AddGroup("B", 10, 11, 12); - _ = groupedCollection.AddGroup("B", 42); + _ = groupedCollection.AddGroup("A", new[] { 23 }); + _ = groupedCollection.AddGroup("B", new[] { 10, 11, 12 }); + _ = groupedCollection.AddGroup("B", new[] { 42 }); - _ = groupedCollection.ElementAt("B", index); + _ = groupedCollection.FirstGroupByKey("B")[index]; } [TestMethod] @@ -102,9 +102,9 @@ public void ElementAt_WhenGroupDoesNotExist_ShouldThrow() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); + _ = groupedCollection.AddGroup("A", new[] { 23 }); - _ = groupedCollection.ElementAt("I do not exist", 0); + _ = groupedCollection.FirstGroupByKey("I do not exist")[0]; } [TestMethod] @@ -112,49 +112,21 @@ public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue( { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 23); - _ = groupedCollection.AddGroup("B", 10, 11, 12); - _ = groupedCollection.AddGroup("B", 42); + _ = groupedCollection.AddGroup("A", new[] { 23 }); + _ = groupedCollection.AddGroup("B", new[] { 10, 11, 12 }); + _ = groupedCollection.AddGroup("B", new[] { 42 }); - int result = groupedCollection.ElementAt("B", 2); + int result = groupedCollection.FirstGroupByKey("B")[2]; Assert.AreEqual(result, 12); } - [TestMethod] - [DataRow(-1)] - [DataRow(3)] - public void ElementAtOrDefault_WhenGroupExistsAndIndexOutOfRange_ShouldReturnDefaultValue(int index) - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 23); - _ = groupedCollection.AddGroup("B", 10, 11, 12); - _ = groupedCollection.AddGroup("B", 42); - - int result = groupedCollection.ElementAtOrDefault("B", index); - - Assert.AreEqual(result, 0); - } - - [TestMethod] - public void ElementAtOrDefault_WhenGroupDoesNotExist_ShouldReturnDefaultValue() - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 23); - - int result = groupedCollection.ElementAtOrDefault("I do not exist", 0); - - Assert.AreEqual(result, 0); - } - [TestMethod] public void AddGroup_WithItem_ShouldAddGroup() { ObservableGroupedCollection groupedCollection = new(); - ObservableGroup addedGroup = groupedCollection.AddGroup("new key", 23); + ObservableGroup addedGroup = groupedCollection.AddGroup("new key", new[] { 23 }); Assert.IsNotNull(addedGroup); Assert.AreEqual(addedGroup.Key, "new key"); @@ -180,7 +152,7 @@ public void AddGroup_WithParamsCollection_ShouldAddGroup() { ObservableGroupedCollection groupedCollection = new(); - ObservableGroup addedGroup = groupedCollection.AddGroup("new key", 23, 10, 42); + ObservableGroup addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 }); Assert.IsNotNull(addedGroup); Assert.AreEqual(addedGroup.Key, "new key"); @@ -206,11 +178,11 @@ public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGr { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); - ObservableGroup targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + ObservableGroup targetGroup = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); - _ = groupedCollection.AddGroup("C", 7, 8); + _ = groupedCollection.AddGroup("C", new[] { 7, 8 }); ObservableGroup addedGroup = groupedCollection.AddItem("B", 23); @@ -235,12 +207,12 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); - ObservableGroup targetGroup = groupedCollection.AddGroup("B", 4, 5, 6); + ObservableGroup targetGroup = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); - _ = groupedCollection.AddGroup("B", 7, 8, 9); - _ = groupedCollection.AddGroup("C", 10, 11); + _ = groupedCollection.AddGroup("B", new[] { 7, 8, 9 }); + _ = groupedCollection.AddGroup("C", new[] { 10, 11 }); ObservableGroup addedGroup = groupedCollection.AddItem("B", 23); @@ -263,120 +235,12 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis CollectionAssert.AreEqual(groupedCollection[3], new[] { 10, 11 }); } - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void InsertItem_WhenGroupDoesNotExist_ShouldThrow() - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 1, 2, 3); - - _ = groupedCollection.InsertItem("I do not exist", 0, 23); - } - - [TestMethod] - [DataRow(-1)] - [DataRow(4)] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void InsertItem_WhenIndexOutOfRange_ShouldThrow(int index) - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 1, 2, 3); - - _ = groupedCollection.InsertItem("A", index, 23); - } - - [TestMethod] - [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) - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 4, 5); - - ObservableGroup targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); - - _ = groupedCollection.AddGroup("B", 6, 7); - - ObservableGroup group = groupedCollection.InsertItem("B", index, 23); - - Assert.AreSame(group, targetGroup); - - Assert.AreEqual(groupedCollection.Count, 3); - - Assert.AreEqual(groupedCollection[0].Key, "A"); - CollectionAssert.AreEqual(groupedCollection[0], new[] { 4, 5 }); - - Assert.AreEqual(groupedCollection[1].Key, "B"); - CollectionAssert.AreEqual(groupedCollection[1], expecteGroupValues); - - Assert.AreEqual(groupedCollection[2].Key, "B"); - CollectionAssert.AreEqual(groupedCollection[2], new[] { 6, 7 }); - } - - [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void SetItem_WhenGroupDoesNotExist_ShouldThrow() - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 1, 2, 3); - - _ = groupedCollection.SetItem("I do not exist", 0, 23); - } - - [TestMethod] - [DataRow(-1)] - [DataRow(3)] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void SetItem_WhenIndexOutOfRange_ShouldThrow(int index) - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 1, 2, 3); - - _ = groupedCollection.SetItem("A", index, 23); - } - - [TestMethod] - [DataRow(0, new[] { 23, 2, 3 })] - [DataRow(1, new[] { 1, 23, 3 })] - [DataRow(2, new[] { 1, 2, 23 })] - public void SetItem_WithValidIndex_WithSeveralGroups_ShouldReplaceItemInFirstGroup(int index, int[] expectedGroupValues) - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", 4, 5); - - ObservableGroup targetGroup = groupedCollection.AddGroup("B", 1, 2, 3); - - _ = groupedCollection.AddGroup("B", 6, 7); - - ObservableGroup? group = groupedCollection.SetItem("B", index, 23); - - Assert.AreSame(group, targetGroup); - - Assert.AreEqual(groupedCollection.Count, 3); - - Assert.AreEqual(groupedCollection[0].Key, "A"); - CollectionAssert.AreEqual(groupedCollection[0], new[] { 4, 5 }); - - Assert.AreEqual(groupedCollection[1].Key, "B"); - CollectionAssert.AreEqual(groupedCollection[1], expectedGroupValues); - - Assert.AreEqual(groupedCollection[2].Key, "B"); - CollectionAssert.AreEqual(groupedCollection[2], new[] { 6, 7 }); - } - [TestMethod] public void RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); groupedCollection.RemoveGroup("I do not exist"); @@ -390,8 +254,8 @@ public void RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); - _ = groupedCollection.AddGroup("B", 4, 5, 6); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); + _ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); groupedCollection.RemoveGroup("B"); @@ -405,9 +269,9 @@ public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); - _ = groupedCollection.AddGroup("B", 4, 5, 6); - _ = groupedCollection.AddGroup("B", 7, 8); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); + _ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); + _ = groupedCollection.AddGroup("B", new[] { 7, 8 }); groupedCollection.RemoveGroup("B"); @@ -427,8 +291,8 @@ public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfE { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); - _ = groupedCollection.AddGroup("B", 4, 5, 6); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); + _ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); groupedCollection.RemoveItem("I do not exist", 8, removeGroupIfEmpty); @@ -448,8 +312,8 @@ public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool r { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); - _ = groupedCollection.AddGroup("B", 4, 5, 6); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); + _ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); groupedCollection.RemoveItem("B", 8, removeGroupIfEmpty); @@ -469,8 +333,8 @@ public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool remo { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); - _ = groupedCollection.AddGroup("B", 4, 5, 6); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); + _ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 }); groupedCollection.RemoveItem("B", 5, removeGroupIfEmpty); @@ -490,8 +354,8 @@ public void RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool rem { ObservableGroupedCollection groupedCollection = new(); - _ = groupedCollection.AddGroup("A", 1, 2, 3); - _ = groupedCollection.AddGroup("B", 4); + _ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 }); + _ = groupedCollection.AddGroup("B", new[] { 4 }); groupedCollection.RemoveItem("B", 4, removeGroupIfEmpty); From f6a0f3fae8893e88bcd731dbd78d88dc313fd07a Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 30 Mar 2022 15:02:52 +0200 Subject: [PATCH 14/22] Add null checks to public APIs --- .../ObservableGroupedCollectionExtensions.cs | 72 +++++++++++++++++++ ...ervableGroupedCollection{TKey,TElement}.cs | 10 ++- .../ObservableGroup{TKey,TElement}.cs | 10 +++ ...ervableGroupedCollection{TKey,TElement}.cs | 13 ++-- .../ReadOnlyObservableGroup{TKey,TElement}.cs | 4 ++ 5 files changed, 102 insertions(+), 7 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs index 31793b3fd..c2d005219 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs @@ -24,10 +24,14 @@ public static class ObservableGroupedCollectionExtensions /// The source instance. /// The key of the group to query. /// The first group matching . + /// Thrown if or are . /// The target group does not exist. public static ObservableGroup FirstGroupByKey(this ObservableGroupedCollection source, TKey key) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); if (group is null) @@ -52,9 +56,13 @@ static void ThrowArgumentExceptionForKeyNotFound() /// The source instance. /// The key of the group to query. /// The first group matching or null. + /// Thrown if or are . public static ObservableGroup? FirstGroupByKeyOrDefault(this ObservableGroupedCollection source, TKey key) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + if (source.TryGetList(out List>? list)) { foreach (ObservableGroup? group in list) @@ -85,9 +93,13 @@ static void ThrowArgumentExceptionForKeyNotFound() /// The source instance. /// The key of the group to add. /// The added . + /// Thrown if or are . public static ObservableGroup AddGroup(this ObservableGroupedCollection source, TKey key) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ObservableGroup group = new(key); source.Add(group); @@ -103,9 +115,13 @@ public static ObservableGroup AddGroup(this Obse /// The source instance. /// The group of items to add. /// The added . + /// Thrown if or are . public static ObservableGroup AddGroup(this ObservableGroupedCollection source, IGrouping grouping) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(grouping); + ObservableGroup group = new(grouping); source.Add(group); @@ -122,9 +138,14 @@ public static ObservableGroup AddGroup(this Obse /// The key of the group where will be added. /// The collection to add. /// The added . + /// Thrown if , or are . public static ObservableGroup AddGroup(this ObservableGroupedCollection source, TKey key, IEnumerable collection) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(collection); + ObservableGroup group = new(key, collection); source.Add(group); @@ -140,9 +161,13 @@ public static ObservableGroup AddGroup(this Obse /// The source instance. /// The key of the group to add. /// The added . + /// Thrown if or are . public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -197,9 +222,13 @@ static ObservableGroup InsertGroupFallback(ObservableGroupedColl /// The source instance. /// The group of items to add. /// The added . + /// Thrown if or are . public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, IGrouping grouping) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(grouping); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -255,9 +284,14 @@ static ObservableGroup InsertGroupFallback(ObservableGroupedColl /// The key of the group where will be added. /// The collection to add. /// The added . + /// Thrown if , or are . public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key, IEnumerable collection) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(collection); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -313,9 +347,14 @@ static ObservableGroup InsertGroupFallback(ObservableGroupedColl /// The key of the group to add. /// The instance to insert at the right position. /// The added . + /// Thrown if , or are . public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key, IComparer comparer) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(comparer); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -371,9 +410,14 @@ static ObservableGroup InsertGroupFallback(ObservableGroupedColl /// The group of items to add. /// The instance to insert at the right position. /// The added . + /// Thrown if , or are . public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, IGrouping grouping, IComparer comparer) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(grouping); + ArgumentNullException.ThrowIfNull(comparer); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -430,9 +474,15 @@ static ObservableGroup InsertGroupFallback(ObservableGroupedColl /// The instance to insert at the right position. /// The collection to add. /// The added . + /// Thrown if , , or are . public static ObservableGroup InsertGroup(this ObservableGroupedCollection source, TKey key, IComparer comparer, IEnumerable collection) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(comparer); + ArgumentNullException.ThrowIfNull(collection); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -489,9 +539,13 @@ static ObservableGroup InsertGroupFallback(ObservableGroupedColl /// 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. + /// Thrown if or are . public static ObservableGroup AddItem(this ObservableGroupedCollection source, TKey key, TElement item) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); if (group is null) @@ -517,9 +571,13 @@ public static ObservableGroup AddItem(this Obser /// The key of the group where to insert . /// The item to add. /// The instance of the which will receive the value. + /// Thrown if or are . public static ObservableGroup InsertItem(this ObservableGroupedCollection source, TKey key, TElement item) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); if (group is null) @@ -580,6 +638,7 @@ static void InsertItemFallback(ObservableCollection source, TElement i /// The item to add. /// The instance to compare elements. /// The instance of the which will receive the value. + /// Thrown if , , or are . public static ObservableGroup InsertItem( this ObservableGroupedCollection source, TKey key, @@ -588,6 +647,11 @@ public static ObservableGroup InsertItem( IComparer itemComparer) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + ArgumentNullException.ThrowIfNull(keyComparer); + ArgumentNullException.ThrowIfNull(itemComparer); + ObservableGroup? group = source.FirstGroupByKeyOrDefault(key); if (group is null) @@ -645,9 +709,13 @@ static void InsertItemFallback(ObservableCollection source, TElement i /// The type of the items in the collection. /// The source instance. /// The key of the group to remove. + /// Thrown if or are . public static void RemoveGroup(this ObservableGroupedCollection source, TKey key) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + if (source.TryGetList(out List>? list)) { int index = 0; @@ -697,9 +765,13 @@ static void RemoveGroupFallback(ObservableGroupedCollection source /// 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. + /// Thrown if or are . public static void RemoveItem(this ObservableGroupedCollection source, TKey key, TValue item, bool removeGroupIfEmpty = true) where TKey : notnull { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.For.ThrowIfNull(key); + if (source.TryGetList(out List>? list)) { int index = 0; diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs index ea83aca33..86d3a76a1 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs @@ -29,16 +29,20 @@ public ObservableGroupedCollection() /// Initializes a new instance of the class. /// /// The initial data to add in the grouped collection. + /// Thrown if is . public ObservableGroupedCollection(IEnumerable> collection) - : base(collection.Select(static group => new ObservableGroup(group))) + : base(collection?.Select(static group => new ObservableGroup(group))!) { } /// IEnumerable ILookup.this[TKey key] { - // TODO: optimize this - get => Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + get + { + // TODO: optimize this + return Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + } } /// diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs index 9b2cce3b6..959aa7332 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroup{TKey,TElement}.cs @@ -26,8 +26,11 @@ public sealed class ObservableGroup : ObservableCollection class. /// /// The key for the group. + /// Thrown if is . public ObservableGroup(TKey key) { + ArgumentNullException.For.ThrowIfNull(key); + this.key = key; } @@ -35,6 +38,7 @@ public ObservableGroup(TKey key) /// Initializes a new instance of the class. /// /// The grouping to fill the group. + /// Thrown if is . public ObservableGroup(IGrouping grouping) : base(grouping) { @@ -46,9 +50,12 @@ public ObservableGroup(IGrouping grouping) /// /// The key for the group. /// The initial collection of data to add to the group. + /// Thrown if or are . public ObservableGroup(TKey key, IEnumerable collection) : base(collection) { + ArgumentNullException.For.ThrowIfNull(key); + this.key = key; } @@ -57,11 +64,14 @@ public ObservableGroup(TKey key, IEnumerable collection) /// /// Gets or sets the key of the group. /// + /// Thrown if is . public TKey Key { get => this.key; set { + ArgumentNullException.For.ThrowIfNull(value); + if (!EqualityComparer.Default.Equals(this.key!, value)) { this.key = value; diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs index 8ba3e2208..7248f4d91 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs @@ -23,16 +23,18 @@ public sealed class ReadOnlyObservableGroupedCollection : ReadOn /// Initializes a new instance of the class. /// /// The source collection to wrap. + /// Thrown if is . public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) - : base(new ObservableCollection>(collection.Select(static g => new ReadOnlyObservableGroup(g)))) + : base(new ObservableCollection>(collection?.Select(static g => new ReadOnlyObservableGroup(g))!)) { - collection.CollectionChanged += OnSourceCollectionChanged; + collection!.CollectionChanged += OnSourceCollectionChanged; } /// /// Initializes a new instance of the class. /// /// The source collection to wrap. + /// Thrown if is . public ReadOnlyObservableGroupedCollection(ObservableCollection> collection) : base(collection) { @@ -41,8 +43,11 @@ public ReadOnlyObservableGroupedCollection(ObservableCollection IEnumerable ILookup.this[TKey key] { - // TODO: optimize this - get => Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + get + { + // TODO: optimize this + return Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + } } /// diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs index 3befc40c9..27f7f04f6 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroup{TKey,TElement}.cs @@ -21,9 +21,12 @@ public sealed class ReadOnlyObservableGroup : ReadOnlyObservable /// /// The key of the group. /// The collection of items to add in the group. + /// Thrown if or are . public ReadOnlyObservableGroup(TKey key, ObservableCollection collection) : base(collection) { + ArgumentNullException.For.ThrowIfNull(key); + Key = key; } @@ -31,6 +34,7 @@ public ReadOnlyObservableGroup(TKey key, ObservableCollection collecti /// Initializes a new instance of the class. /// /// The to wrap. + /// Thrown if is . public ReadOnlyObservableGroup(ObservableGroup group) : base(group) { From bf31152d4784e65b923d2872b7c83432752b3933 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 30 Mar 2022 15:29:58 +0200 Subject: [PATCH 15/22] Optimize ILookup implementations --- .../ObservableGroupedCollectionExtensions.cs | 20 ++++----- ...ervableGroupedCollection{TKey,TElement}.cs | 13 ++++-- ...ervableGroupedCollection{TKey,TElement}.cs | 43 +++++++++++++++++-- 3 files changed, 58 insertions(+), 18 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs index c2d005219..526c02851 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs @@ -17,7 +17,7 @@ namespace CommunityToolkit.Mvvm.Collections; public static class ObservableGroupedCollectionExtensions { /// - /// Return the first group with key. + /// Returns the first group with key. /// /// The type of the group key. /// The type of the items in the collection. @@ -49,13 +49,13 @@ static void ThrowArgumentExceptionForKeyNotFound() } /// - /// Return the first group with key or null if not found. + /// Returns the first group with key or 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. + /// The first group matching or . /// Thrown if or are . public static ObservableGroup? FirstGroupByKeyOrDefault(this ObservableGroupedCollection source, TKey key) where TKey : notnull @@ -65,7 +65,7 @@ static void ThrowArgumentExceptionForKeyNotFound() if (source.TryGetList(out List>? list)) { - foreach (ObservableGroup? group in list) + foreach (ObservableGroup group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { @@ -77,12 +77,12 @@ static void ThrowArgumentExceptionForKeyNotFound() } [MethodImpl(MethodImplOptions.NoInlining)] - static ObservableGroup? FirstOrDefaultFallback(ObservableGroupedCollection source, TKey key) + static ObservableGroup? FirstGroupByKeyOrDefaultFallback(ObservableGroupedCollection source, TKey key) { return Enumerable.FirstOrDefault>(source, group => EqualityComparer.Default.Equals(group.Key, key)); } - return FirstOrDefaultFallback(source, key); + return FirstGroupByKeyOrDefaultFallback(source, key); } /// @@ -720,7 +720,7 @@ public static void RemoveGroup(this ObservableGroupedCollection? group in list) + foreach (ObservableGroup group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { @@ -739,7 +739,7 @@ static void RemoveGroupFallback(ObservableGroupedCollection source { int index = 0; - foreach (ObservableGroup? group in source) + foreach (ObservableGroup group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) { @@ -776,7 +776,7 @@ public static void RemoveItem(this ObservableGroupedCollection? group in list) + foreach (ObservableGroup group in list) { if (EqualityComparer.Default.Equals(group.Key, key)) { @@ -800,7 +800,7 @@ static void RemoveItemFallback(ObservableGroupedCollection source, { int index = 0; - foreach (ObservableGroup? group in source) + foreach (ObservableGroup group in source) { if (EqualityComparer.Default.Equals(group.Key, key)) { diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs index 86d3a76a1..3879a736d 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollection{TKey,TElement}.cs @@ -40,8 +40,14 @@ IEnumerable ILookup.this[TKey key] { get { - // TODO: optimize this - return Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + IEnumerable? result = null; + + if (key is not null) + { + result = this.FirstGroupByKeyOrDefault(key); + } + + return result ?? Enumerable.Empty(); } } @@ -61,8 +67,7 @@ internal bool TryGetList([NotNullWhen(true)] out List bool ILookup.Contains(TKey key) { - // TODO: optimize this - return Enumerable.Any>(this, item => EqualityComparer.Default.Equals(item.Key, key)); + return key is not null && this.FirstGroupByKey(key) is not null; } /// diff --git a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs index 7248f4d91..e8f9551ca 100644 --- a/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs +++ b/CommunityToolkit.Mvvm/Collections/ReadOnlyObservableGroupedCollection{TKey,TElement}.cs @@ -8,6 +8,7 @@ using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; namespace CommunityToolkit.Mvvm.Collections; @@ -45,16 +46,21 @@ IEnumerable ILookup.this[TKey key] { get { - // TODO: optimize this - return Enumerable.FirstOrDefault>(this, item => EqualityComparer.Default.Equals(item.Key, key)) ?? Enumerable.Empty(); + IEnumerable? result = null; + + if (key is not null) + { + result = FirstGroupByKeyOrDefault(key); + } + + return result ?? Enumerable.Empty(); } } /// bool ILookup.Contains(TKey key) { - // TODO: optimize this - return Enumerable.Any>(this, item => EqualityComparer.Default.Equals(item.Key, key)); + return key is not null && FirstGroupByKeyOrDefault(key) is not null; } /// @@ -153,4 +159,33 @@ static void ThrowNotSupportedExceptionForRangeOperation() break; } } + + /// + /// Returns the first group with key or if not found. + /// + /// The key of the group to query (assumed not to be ). + /// The first group matching . + private IEnumerable? FirstGroupByKeyOrDefault(TKey key) + { + if (Items is List> list) + { + foreach (ReadOnlyObservableGroup group in list) + { + if (EqualityComparer.Default.Equals(group.Key, key)) + { + return group; + } + } + + return null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static IEnumerable? FirstGroupByKeyOrDefaultFallback(ReadOnlyObservableGroupedCollection source, TKey key) + { + return Enumerable.FirstOrDefault>(source, group => EqualityComparer.Default.Equals(group.Key, key)); + } + + return FirstGroupByKeyOrDefaultFallback(this, key); + } } From 0e3be7e80cb0812221fc3d43bcf2466f9dad4e07 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 Apr 2022 15:25:08 +0200 Subject: [PATCH 16/22] Add tests for ArgumentNullException-s in collections --- .../Collections/IntGroup.cs | 11 +++- .../Collections/ObservableGroupTests.cs | 50 +++++++++++++++++-- .../ObservableGroupedCollectionTests.cs | 8 +++ .../ReadOnlyObservableGroupTests.cs | 25 ++++++++++ ...eadOnlyObservableGroupedCollectionTests.cs | 14 ++++++ 5 files changed, 103 insertions(+), 5 deletions(-) diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/IntGroup.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/IntGroup.cs index e76bee1da..df4d2049d 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/IntGroup.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/IntGroup.cs @@ -7,13 +7,22 @@ namespace CommunityToolkit.Mvvm.UnitTests; -public class IntGroup : List, IGrouping +/// +/// A simple implementation for and values. +/// +internal sealed class IntGroup : List, IGrouping { + /// + /// Creates a new instance with the specified parameters. + /// + /// The group key. + /// The group values. public IntGroup(string key, IEnumerable collection) : base(collection) { Key = key; } + /// public string Key { get; } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs index 2022b4459..8a6ebd65e 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.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.Specialized; using System.Linq; using CommunityToolkit.Mvvm.Collections; @@ -47,7 +48,8 @@ public void Add_ShouldRaiseEvent() bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; ObservableGroup group = new("key", source); - ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; + + group.CollectionChanged += (s, e) => collectionChangedEventRaised = true; group.Add(4); @@ -62,7 +64,8 @@ public void Update_ShouldRaiseEvent() bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; ObservableGroup group = new("key", source); - ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; + + group.CollectionChanged += (s, e) => collectionChangedEventRaised = true; group[1] = 4; @@ -77,7 +80,8 @@ public void Remove_ShouldRaiseEvent() bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; ObservableGroup? group = new("key", source); - ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; + + group.CollectionChanged += (s, e) => collectionChangedEventRaised = true; _ = group.Remove(1); @@ -92,7 +96,8 @@ public void Clear_ShouldRaiseEvent() bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; ObservableGroup? group = new("key", source); - ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; + + group.CollectionChanged += (s, e) => collectionChangedEventRaised = true; group.Clear(); @@ -112,4 +117,41 @@ public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) Assert.AreEqual(iReadOnlyObservableGroup.Key, "key"); Assert.AreEqual(iReadOnlyObservableGroup.Count, count); } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullKey() + { + _ = new ObservableGroup((string)null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullGroup() + { + _ = new ObservableGroup((IGrouping)null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullKeyWithNotNullElements() + { + _ = new ObservableGroup(null!, new int[0]); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NotNullKeyWithNullElements() + { + _ = new ObservableGroup("A", null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullKeySetter() + { + ObservableGroup group = new("A"); + + group.Key = null!; + } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs index 1a845905f..51160a0ca 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.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; using CommunityToolkit.Mvvm.Collections; @@ -38,4 +39,11 @@ public void Ctor_WithGroups_ShouldHaveExpectedValues() Assert.AreEqual(groupCollection[1].Key, "B"); CollectionAssert.AreEqual(groupCollection[1], new[] { 2, 4, 6 }); } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullCollection() + { + _ = new ObservableGroupedCollection(null!); + } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs index b6074102e..1f3745289 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs @@ -52,6 +52,7 @@ public void Add_ShouldRaiseEvent() int[] source = new[] { 1, 2, 3 }; ObservableGroup sourceGroup = new("key", source); ReadOnlyObservableGroup group = new(sourceGroup); + ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; sourceGroup.Add(4); @@ -69,6 +70,7 @@ public void Update_ShouldRaiseEvent() int[] source = new[] { 1, 2, 3 }; ObservableGroup sourceGroup = new("key", source); ReadOnlyObservableGroup group = new(sourceGroup); + ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; sourceGroup[1] = 4; @@ -86,6 +88,7 @@ public void Remove_ShouldRaiseEvent() int[] source = new[] { 1, 2, 3 }; ObservableGroup sourceGroup = new("key", source); ReadOnlyObservableGroup group = new(sourceGroup); + ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; _ = sourceGroup.Remove(1); @@ -103,6 +106,7 @@ public void Clear_ShouldRaiseEvent() int[] source = new[] { 1, 2, 3 }; ObservableGroup sourceGroup = new("key", source); ReadOnlyObservableGroup group = new(sourceGroup); + ((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true; sourceGroup.Clear(); @@ -125,4 +129,25 @@ public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) Assert.AreEqual(iReadOnlyObservableGroup.Key, "key"); Assert.AreEqual(iReadOnlyObservableGroup.Count, count); } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullKeyWithNotNullElements() + { + _ = new ReadOnlyObservableGroup(null!, new ObservableCollection()); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NotNullKeyWithNullElements() + { + _ = new ReadOnlyObservableGroup("A", null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullGroup() + { + _ = new ReadOnlyObservableGroup(null!); + } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs index b15c66673..814cc65b5 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs @@ -462,4 +462,18 @@ private static bool IsReplaceEventValid(NotifyCollectionChangedEventArgs args, I } private static bool IsResetEventValid(NotifyCollectionChangedEventArgs args) => args.Action == NotifyCollectionChangedAction.Reset && args.NewItems == null && args.OldItems == null; + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullCollectionWithObservableGroups() + { + _ = new ReadOnlyObservableGroupedCollection((ObservableCollection>)null!); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void Ctor_NullCollectionWithReadOnlyObservableGroups() + { + _ = new ReadOnlyObservableGroupedCollection((ObservableCollection>)null!); + } } From 274a3c0174481ab622f54b501f76e93f1b18e61c Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 Apr 2022 15:27:45 +0200 Subject: [PATCH 17/22] Minor code refactoring --- .../{ObservableGroupTests.cs => Test_ObservableGroup.cs} | 5 ++--- ...ollectionTests.cs => Test_ObservableGroupedCollection.cs} | 4 ++-- ...ests.cs => Test_ObservableGroupedCollectionExtensions.cs} | 5 ++--- ...servableGroupTests.cs => Test_ReadOnlyObservableGroup.cs} | 4 ++-- ...nTests.cs => Test_ReadOnlyObservableGroupedCollection.cs} | 4 ++-- 5 files changed, 10 insertions(+), 12 deletions(-) rename tests/CommunityToolkit.Mvvm.UnitTests/Collections/{ObservableGroupTests.cs => Test_ObservableGroup.cs} (97%) rename tests/CommunityToolkit.Mvvm.UnitTests/Collections/{ObservableGroupedCollectionTests.cs => Test_ObservableGroupedCollection.cs} (93%) rename tests/CommunityToolkit.Mvvm.UnitTests/Collections/{ObservableGroupedCollectionExtensionsTests.cs => Test_ObservableGroupedCollectionExtensions.cs} (99%) rename tests/CommunityToolkit.Mvvm.UnitTests/Collections/{ReadOnlyObservableGroupTests.cs => Test_ReadOnlyObservableGroup.cs} (98%) rename tests/CommunityToolkit.Mvvm.UnitTests/Collections/{ReadOnlyObservableGroupedCollectionTests.cs => Test_ReadOnlyObservableGroupedCollection.cs} (99%) diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs similarity index 97% rename from tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs rename to tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs index 8a6ebd65e..0b7278e07 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs @@ -3,15 +3,14 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Specialized; using System.Linq; using CommunityToolkit.Mvvm.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace CommunityToolkit.Mvvm.UnitTests; +namespace CommunityToolkit.Mvvm.UnitTests.Collections; [TestClass] -public class ObservableGroupTests +public class Test_ObservableGroup { [TestMethod] public void Ctor_ShouldHaveExpectedState() diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs similarity index 93% rename from tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs rename to tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs index 51160a0ca..f5b7b3b9a 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs @@ -8,10 +8,10 @@ using CommunityToolkit.Mvvm.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace CommunityToolkit.Mvvm.UnitTests; +namespace CommunityToolkit.Mvvm.UnitTests.Collections; [TestClass] -public class ObservableGroupedCollectionTests +public class Test_ObservableGroupedCollection { [TestMethod] public void Ctor_ShouldHaveExpectedValues() diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs similarity index 99% rename from tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs rename to tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs index effd5f214..724ea4c04 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ObservableGroupedCollectionExtensionsTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs @@ -3,14 +3,13 @@ // See the LICENSE file in the project root for more information. using System; -using System.Linq; using CommunityToolkit.Mvvm.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace CommunityToolkit.Mvvm.UnitTests; +namespace CommunityToolkit.Mvvm.UnitTests.Collections; [TestClass] -public class ObservableGroupedCollectionExtensionsTests +public class Test_ObservableGroupedCollectionExtensions { [TestMethod] public void First_WhenGroupExists_ShouldReturnFirstGroup() diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs similarity index 98% rename from tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs rename to tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs index 1f3745289..e2e2f3ca0 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs @@ -9,10 +9,10 @@ using CommunityToolkit.Mvvm.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace CommunityToolkit.Mvvm.UnitTests; +namespace CommunityToolkit.Mvvm.UnitTests.Collections; [TestClass] -public class ReadOnlyObservableGroupTests +public class Test_ReadOnlyObservableGroup { [TestMethod] public void Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState() diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs similarity index 99% rename from tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs rename to tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs index 814cc65b5..99cc5ea04 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/ReadOnlyObservableGroupedCollectionTests.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs @@ -12,10 +12,10 @@ using CommunityToolkit.Mvvm.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace CommunityToolkit.Mvvm.UnitTests; +namespace CommunityToolkit.Mvvm.UnitTests.Collections; [TestClass] -public class ReadOnlyObservableGroupedCollectionTests +public class Test_ReadOnlyObservableGroupedCollection { [TestMethod] public void Ctor_WithEmptySource_ShoudInitializeObject() From dd2a72d4e7da3d95ee53c9c2c757457692a7161f Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 Apr 2022 15:32:16 +0200 Subject: [PATCH 18/22] Rename unit test methods for consistency --- .../Collections/Test_ObservableGroup.cs | 26 +++++----- .../Test_ObservableGroupedCollection.cs | 6 +-- ...t_ObservableGroupedCollectionExtensions.cs | 51 ++++++++----------- .../Test_ReadOnlyObservableGroup.cs | 22 ++++---- ...est_ReadOnlyObservableGroupedCollection.cs | 30 +++++------ 5 files changed, 62 insertions(+), 73 deletions(-) diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs index 0b7278e07..15c28865f 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroup.cs @@ -13,7 +13,7 @@ namespace CommunityToolkit.Mvvm.UnitTests.Collections; public class Test_ObservableGroup { [TestMethod] - public void Ctor_ShouldHaveExpectedState() + public void Test_ObservableGroup_Ctor_ShouldHaveExpectedState() { ObservableGroup group = new("key"); @@ -22,7 +22,7 @@ public void Ctor_ShouldHaveExpectedState() } [TestMethod] - public void Ctor_WithGrouping_ShouldHaveExpectedState() + public void Test_ObservableGroup_Ctor_WithGrouping_ShouldHaveExpectedState() { IntGroup source = new("key", new[] { 1, 2, 3 }); ObservableGroup group = new(source); @@ -32,7 +32,7 @@ public void Ctor_WithGrouping_ShouldHaveExpectedState() } [TestMethod] - public void Ctor_WithCollection_ShouldHaveExpectedState() + public void Test_ObservableGroup_Ctor_WithCollection_ShouldHaveExpectedState() { int[] source = new[] { 1, 2, 3 }; ObservableGroup group = new("key", source); @@ -42,7 +42,7 @@ public void Ctor_WithCollection_ShouldHaveExpectedState() } [TestMethod] - public void Add_ShouldRaiseEvent() + public void Test_ObservableGroup_Add_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -58,7 +58,7 @@ public void Add_ShouldRaiseEvent() } [TestMethod] - public void Update_ShouldRaiseEvent() + public void Test_ObservableGroup_Update_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -74,7 +74,7 @@ public void Update_ShouldRaiseEvent() } [TestMethod] - public void Remove_ShouldRaiseEvent() + public void Test_ObservableGroup_Remove_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -90,7 +90,7 @@ public void Remove_ShouldRaiseEvent() } [TestMethod] - public void Clear_ShouldRaiseEvent() + public void Test_ObservableGroup_Clear_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -108,7 +108,7 @@ public void Clear_ShouldRaiseEvent() [TestMethod] [DataRow(0)] [DataRow(3)] - public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) + public void Test_ObservableGroup_IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) { ObservableGroup group = new("key", Enumerable.Range(0, count)); IReadOnlyObservableGroup iReadOnlyObservableGroup = group; @@ -119,35 +119,35 @@ public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullKey() + public void Test_ObservableGroup_Ctor_NullKey() { _ = new ObservableGroup((string)null!); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullGroup() + public void Test_ObservableGroup_Ctor_NullGroup() { _ = new ObservableGroup((IGrouping)null!); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullKeyWithNotNullElements() + public void Test_ObservableGroup_Ctor_NullKeyWithNotNullElements() { _ = new ObservableGroup(null!, new int[0]); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NotNullKeyWithNullElements() + public void Test_ObservableGroup_Ctor_NotNullKeyWithNullElements() { _ = new ObservableGroup("A", null!); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullKeySetter() + public void Test_ObservableGroup_Ctor_NullKeySetter() { ObservableGroup group = new("A"); diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs index f5b7b3b9a..0576c9bd3 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollection.cs @@ -14,7 +14,7 @@ namespace CommunityToolkit.Mvvm.UnitTests.Collections; public class Test_ObservableGroupedCollection { [TestMethod] - public void Ctor_ShouldHaveExpectedValues() + public void Test_ObservableGroupedCollection_Ctor_ShouldHaveExpectedValues() { ObservableGroupedCollection groupCollection = new(); @@ -22,7 +22,7 @@ public void Ctor_ShouldHaveExpectedValues() } [TestMethod] - public void Ctor_WithGroups_ShouldHaveExpectedValues() + public void Test_ObservableGroupedCollection_Ctor_WithGroups_ShouldHaveExpectedValues() { List> groups = new() { @@ -42,7 +42,7 @@ public void Ctor_WithGroups_ShouldHaveExpectedValues() [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullCollection() + public void Test_ObservableGroupedCollection_Ctor_NullCollection() { _ = new ObservableGroupedCollection(null!); } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs index 724ea4c04..bf3a4ea99 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs @@ -12,7 +12,7 @@ namespace CommunityToolkit.Mvvm.UnitTests.Collections; public class Test_ObservableGroupedCollectionExtensions { [TestMethod] - public void First_WhenGroupExists_ShouldReturnFirstGroup() + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExists_ShouldReturnFirstGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -29,7 +29,7 @@ public void First_WhenGroupExists_ShouldReturnFirstGroup() [TestMethod] [ExpectedException(typeof(InvalidOperationException))] - public void First_WhenGroupDoesNotExist_ShouldThrow() + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupDoesNotExist_ShouldThrow() { ObservableGroupedCollection groupedCollection = new(); @@ -39,7 +39,7 @@ public void First_WhenGroupDoesNotExist_ShouldThrow() } [TestMethod] - public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup() + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKeyOrDefault_WhenGroupExists_ShouldReturnFirstGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -55,7 +55,7 @@ public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup() } [TestMethod] - public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKeyOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() { ObservableGroupedCollection groupedCollection = new(); @@ -67,7 +67,7 @@ public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull() } [TestMethod] - public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue() + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue() { ObservableGroupedCollection groupedCollection = new(); @@ -84,7 +84,7 @@ public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue [DataRow(-1)] [DataRow(3)] [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int index) + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int index) { ObservableGroupedCollection groupedCollection = new(); @@ -96,18 +96,7 @@ public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int in } [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void ElementAt_WhenGroupDoesNotExist_ShouldThrow() - { - ObservableGroupedCollection groupedCollection = new(); - - _ = groupedCollection.AddGroup("A", new[] { 23 }); - - _ = groupedCollection.FirstGroupByKey("I do not exist")[0]; - } - - [TestMethod] - public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue() + public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExistsAndIndexInRange_ShouldReturnValue() { ObservableGroupedCollection groupedCollection = new(); @@ -121,7 +110,7 @@ public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue( } [TestMethod] - public void AddGroup_WithItem_ShouldAddGroup() + public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithItem_ShouldAddGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -134,7 +123,7 @@ public void AddGroup_WithItem_ShouldAddGroup() } [TestMethod] - public void AddGroup_WithCollection_ShouldAddGroup() + public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithCollection_ShouldAddGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -147,7 +136,7 @@ public void AddGroup_WithCollection_ShouldAddGroup() } [TestMethod] - public void AddGroup_WithParamsCollection_ShouldAddGroup() + public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithParamsCollection_ShouldAddGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -160,7 +149,7 @@ public void AddGroup_WithParamsCollection_ShouldAddGroup() } [TestMethod] - public void AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() + public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -173,7 +162,7 @@ public void AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() } [TestMethod] - public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGroup() + public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -202,7 +191,7 @@ public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGr } [TestMethod] - public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExistingGroup() + public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExistingGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -235,7 +224,7 @@ public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExis } [TestMethod] - public void RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() + public void Test_ObservableGroupedCollectionExtensions_RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() { ObservableGroupedCollection groupedCollection = new(); @@ -249,7 +238,7 @@ public void RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() } [TestMethod] - public void RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup() + public void Test_ObservableGroupedCollectionExtensions_RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -264,7 +253,7 @@ public void RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup() } [TestMethod] - public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() + public void Test_ObservableGroupedCollectionExtensions_RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() { ObservableGroupedCollection groupedCollection = new(); @@ -286,7 +275,7 @@ public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup() [TestMethod] [DataRow(true)] [DataRow(false)] - public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) + public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) { ObservableGroupedCollection groupedCollection = new(); @@ -307,7 +296,7 @@ public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfE [TestMethod] [DataRow(true)] [DataRow(false)] - public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) + public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty) { ObservableGroupedCollection groupedCollection = new(); @@ -328,7 +317,7 @@ public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool r [TestMethod] [DataRow(true)] [DataRow(false)] - public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool removeGroupIfEmpty) + public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool removeGroupIfEmpty) { ObservableGroupedCollection groupedCollection = new(); @@ -349,7 +338,7 @@ public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool remo [TestMethod] [DataRow(true, true)] [DataRow(false, false)] - public void RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool removeGroupIfEmpty, bool expectGroupRemoved) + public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool removeGroupIfEmpty, bool expectGroupRemoved) { ObservableGroupedCollection groupedCollection = new(); diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs index e2e2f3ca0..88da0a1e2 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroup.cs @@ -15,7 +15,7 @@ namespace CommunityToolkit.Mvvm.UnitTests.Collections; public class Test_ReadOnlyObservableGroup { [TestMethod] - public void Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState() + public void Test_ReadOnlyObservableGroup_Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState() { ObservableCollection source = new(new[] { 1, 2, 3 }); ReadOnlyObservableGroup group = new("key", source); @@ -25,7 +25,7 @@ public void Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState() } [TestMethod] - public void Ctor_ObservableGroup_ShouldHaveExpectedInitialState() + public void Test_ReadOnlyObservableGroup_Ctor_ObservableGroup_ShouldHaveExpectedInitialState() { int[] source = new[] { 1, 2, 3 }; ObservableGroup sourceGroup = new("key", source); @@ -36,7 +36,7 @@ public void Ctor_ObservableGroup_ShouldHaveExpectedInitialState() } [TestMethod] - public void Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState() + public void Test_ReadOnlyObservableGroup_Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState() { ObservableCollection source = new() { 1, 2, 3 }; ReadOnlyObservableGroup group = new("key", source); @@ -46,7 +46,7 @@ public void Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState() } [TestMethod] - public void Add_ShouldRaiseEvent() + public void Test_ReadOnlyObservableGroup_Add_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -64,7 +64,7 @@ public void Add_ShouldRaiseEvent() } [TestMethod] - public void Update_ShouldRaiseEvent() + public void Test_ReadOnlyObservableGroup_Update_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -82,7 +82,7 @@ public void Update_ShouldRaiseEvent() } [TestMethod] - public void Remove_ShouldRaiseEvent() + public void Test_ReadOnlyObservableGroup_Remove_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -100,7 +100,7 @@ public void Remove_ShouldRaiseEvent() } [TestMethod] - public void Clear_ShouldRaiseEvent() + public void Test_ReadOnlyObservableGroup_Clear_ShouldRaiseEvent() { bool collectionChangedEventRaised = false; int[] source = new[] { 1, 2, 3 }; @@ -120,7 +120,7 @@ public void Clear_ShouldRaiseEvent() [TestMethod] [DataRow(0)] [DataRow(3)] - public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) + public void Test_ReadOnlyObservableGroup_IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) { ObservableGroup sourceGroup = new("key", Enumerable.Range(0, count)); ReadOnlyObservableGroup group = new(sourceGroup); @@ -132,21 +132,21 @@ public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count) [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullKeyWithNotNullElements() + public void Test_ReadOnlyObservableGroup_Ctor_NullKeyWithNotNullElements() { _ = new ReadOnlyObservableGroup(null!, new ObservableCollection()); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NotNullKeyWithNullElements() + public void Test_ReadOnlyObservableGroup_Ctor_NotNullKeyWithNullElements() { _ = new ReadOnlyObservableGroup("A", null!); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullGroup() + public void Test_ReadOnlyObservableGroup_Ctor_NullGroup() { _ = new ReadOnlyObservableGroup(null!); } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs index 99cc5ea04..a82e65353 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs @@ -18,7 +18,7 @@ namespace CommunityToolkit.Mvvm.UnitTests.Collections; public class Test_ReadOnlyObservableGroupedCollection { [TestMethod] - public void Ctor_WithEmptySource_ShoudInitializeObject() + public void Test_ReadOnlyObservableGroupedCollection_Ctor_WithEmptySource_ShoudInitializeObject() { ObservableGroupedCollection source = new(); ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); @@ -28,7 +28,7 @@ public void Ctor_WithEmptySource_ShoudInitializeObject() } [TestMethod] - public void Ctor_WithObservableGroupedCollection_ShoudInitializeObject() + public void Test_ReadOnlyObservableGroupedCollection_Ctor_WithObservableGroupedCollection_ShoudInitializeObject() { List> groups = new() { @@ -48,7 +48,7 @@ public void Ctor_WithObservableGroupedCollection_ShoudInitializeObject() } [TestMethod] - public void Ctor_WithListOfReadOnlyObservableGroupSource_ShoudInitializeObject() + public void Test_ReadOnlyObservableGroupedCollection_Ctor_WithListOfReadOnlyObservableGroupSource_ShoudInitializeObject() { ObservableCollection> source = new() { @@ -67,7 +67,7 @@ public void Ctor_WithListOfReadOnlyObservableGroupSource_ShoudInitializeObject() } [TestMethod] - public void IListImplementation_Properties_ShoudReturnExpectedValues() + public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_Properties_ShoudReturnExpectedValues() { List> groups = new() { @@ -97,7 +97,7 @@ public void IListImplementation_Properties_ShoudReturnExpectedValues() } [TestMethod] - public void IListImplementation_MutableMethods_ShoudThrow() + public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_MutableMethods_ShoudThrow() { List> groups = new() { @@ -128,7 +128,7 @@ public void IListImplementation_MutableMethods_ShoudThrow() [DataRow(0)] [DataRow(1)] [DataRow(2)] - public void IListImplementation_IndexOf_ShoudReturnExpectedValue(int groupIndex) + public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_IndexOf_ShoudReturnExpectedValue(int groupIndex) { List> groups = new() { @@ -151,7 +151,7 @@ public void IListImplementation_IndexOf_ShoudReturnExpectedValue(int groupIndex) [DataRow(-1, false)] [DataRow(0, true)] [DataRow(1, true)] - public void IListImplementation_Contains_ShoudReturnExpectedValue(int groupIndex, bool expectedResult) + public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_Contains_ShoudReturnExpectedValue(int groupIndex, bool expectedResult) { List> groups = new() { @@ -172,7 +172,7 @@ public void IListImplementation_Contains_ShoudReturnExpectedValue(int groupIndex [TestMethod] [DataRow(0, 0)] [DataRow(3, 3)] - public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int expectedInsertionIndex) + public void Test_ReadOnlyObservableGroupedCollection_AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int expectedInsertionIndex) { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; @@ -215,7 +215,7 @@ public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int exp [DataRow(0)] [DataRow(1)] [DataRow(2)] - public void InsertGroupInSource_ShouldAddGroup(int insertionIndex) + public void Test_ReadOnlyObservableGroupedCollection_InsertGroupInSource_ShouldAddGroup(int insertionIndex) { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; @@ -252,7 +252,7 @@ public void InsertGroupInSource_ShouldAddGroup(int insertionIndex) } [TestMethod] - public void RemoveGroupInSource_ShoudRemoveGroup() + public void Test_ReadOnlyObservableGroupedCollection_RemoveGroupInSource_ShoudRemoveGroup() { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; @@ -292,7 +292,7 @@ public void RemoveGroupInSource_ShoudRemoveGroup() [TestMethod] [DataRow(1, 0)] [DataRow(0, 1)] - public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) + public void Test_ReadOnlyObservableGroupedCollection_MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; @@ -333,7 +333,7 @@ public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) } [TestMethod] - public void ClearSource_ShoudClear() + public void Test_ReadOnlyObservableGroupedCollection_ClearSource_ShoudClear() { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; @@ -368,7 +368,7 @@ public void ClearSource_ShoudClear() } [TestMethod] - public void ReplaceGroupInSource_ShoudReplaceGroup() + public void Test_ReadOnlyObservableGroupedCollection_ReplaceGroupInSource_ShoudReplaceGroup() { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; @@ -465,14 +465,14 @@ private static bool IsReplaceEventValid(NotifyCollectionChangedEventArgs args, I [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullCollectionWithObservableGroups() + public void Test_ReadOnlyObservableGroupedCollection_Ctor_NullCollectionWithObservableGroups() { _ = new ReadOnlyObservableGroupedCollection((ObservableCollection>)null!); } [TestMethod] [ExpectedException(typeof(ArgumentNullException))] - public void Ctor_NullCollectionWithReadOnlyObservableGroups() + public void Test_ReadOnlyObservableGroupedCollection_Ctor_NullCollectionWithReadOnlyObservableGroups() { _ = new ReadOnlyObservableGroupedCollection((ObservableCollection>)null!); } From d1e75b9893405582ebfc62ec81fd5bae9e506daa Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 5 Apr 2022 15:49:56 +0200 Subject: [PATCH 19/22] Add ObservableGroupedCollectionExtensions tests, bug fixes --- .../ObservableGroupedCollectionExtensions.cs | 46 +++++---- ...t_ObservableGroupedCollectionExtensions.cs | 93 +++++++++++++++++++ 2 files changed, 115 insertions(+), 24 deletions(-) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs index 526c02851..d0a31f2ce 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs @@ -584,25 +584,24 @@ public static ObservableGroup InsertItem(this Ob { group = source.InsertGroup(key, new[] { item }); } - else + else if (group.TryGetList(out List? list)) { - if (group.TryGetList(out List? list)) - { - int index = 0; + int index = 0; - foreach (TElement element in list) + foreach (TElement element in list) + { + if (Comparer.Default.Compare(item, element) < 0) { - if (Comparer.Default.Compare(item, element) < 0) - { - break; - } - - index++; + break; } - group.Insert(index, item); + index++; } + group.Insert(index, item); + } + else + { [MethodImpl(MethodImplOptions.NoInlining)] static void InsertItemFallback(ObservableCollection source, TElement item) { @@ -658,25 +657,24 @@ public static ObservableGroup InsertItem( { group = source.InsertGroup(key, keyComparer, new[] { item }); } - else + else if (group.TryGetList(out List? list)) { - if (group.TryGetList(out List? list)) - { - int index = 0; + int index = 0; - foreach (TElement element in list) + foreach (TElement element in list) + { + if (itemComparer.Compare(item, element) < 0) { - if (itemComparer.Compare(item, element) < 0) - { - break; - } - - index++; + break; } - group.Insert(index, item); + index++; } + group.Insert(index, item); + } + else + { [MethodImpl(MethodImplOptions.NoInlining)] static void InsertItemFallback(ObservableCollection source, TElement item, IComparer comparer) { diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs index bf3a4ea99..3086e36f4 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ObservableGroupedCollectionExtensions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using CommunityToolkit.Mvvm.Collections; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -148,6 +149,38 @@ public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithParamsCollec CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup }); } + [TestMethod] + public void Test_ObservableGroupedCollectionExtensions_InsertGroup() + { + ObservableGroupedCollection groupedCollection = new(); + + ObservableGroup a = groupedCollection.InsertGroup("A", new[] { 1, 2, 3 }); + ObservableGroup d = groupedCollection.InsertGroup("D", new[] { 1, 2, 3 }); + ObservableGroup e = groupedCollection.InsertGroup("E", new[] { 1, 2, 3 }); + ObservableGroup b = groupedCollection.InsertGroup("B", new[] { 1, 2, 3 }); + ObservableGroup z = groupedCollection.InsertGroup("Z", new[] { 1, 2, 3 }); + ObservableGroup c = groupedCollection.InsertGroup("C", new[] { 1, 2, 3 }); + + CollectionAssert.AllItemsAreNotNull(new[] { a, d, e, b, z, c }); + CollectionAssert.AreEqual(new[] { a, b, c, d, e, z }, groupedCollection); + } + + [TestMethod] + public void Test_ObservableGroupedCollectionExtensions_InsertGroup_WithGrouping() + { + ObservableGroupedCollection groupedCollection = new(); + + ObservableGroup a = groupedCollection.InsertGroup(new IntGroup("A", new[] { 1, 2, 3 })); + ObservableGroup d = groupedCollection.InsertGroup(new IntGroup("D", new[] { 1, 2, 3 })); + ObservableGroup e = groupedCollection.InsertGroup(new IntGroup("E", new[] { 1, 2, 3 })); + ObservableGroup b = groupedCollection.InsertGroup(new IntGroup("B", new[] { 1, 2, 3 })); + ObservableGroup z = groupedCollection.InsertGroup(new IntGroup("Z", new[] { 1, 2, 3 })); + ObservableGroup c = groupedCollection.InsertGroup(new IntGroup("C", new[] { 1, 2, 3 })); + + CollectionAssert.AllItemsAreNotNull(new[] { a, d, e, b, z, c }); + CollectionAssert.AreEqual(new[] { a, b, c, d, e, z }, groupedCollection); + } + [TestMethod] public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup() { @@ -223,6 +256,66 @@ public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenSeveralTarget CollectionAssert.AreEqual(groupedCollection[3], new[] { 10, 11 }); } + [TestMethod] + public void Test_ObservableGroupedCollectionExtensions_InsertItem() + { + ObservableGroupedCollection groupedCollection = new(); + + ObservableGroup group1 = groupedCollection.InsertItem("A", 1); + ObservableGroup group2 = groupedCollection.InsertItem("A", 2); + ObservableGroup group6 = groupedCollection.InsertItem("A", 6); + ObservableGroup group4 = groupedCollection.InsertItem("A", 4); + ObservableGroup group3 = groupedCollection.InsertItem("A", 3); + ObservableGroup group99 = groupedCollection.InsertItem("A", 99); + ObservableGroup group8 = groupedCollection.InsertItem("B", 8); + ObservableGroup group7 = groupedCollection.InsertItem("B", 7); + + Assert.AreEqual(2, groupedCollection.Count); + CollectionAssert.AllItemsAreNotNull(new[] { group1, group2, group6, group4, group3, group99, group8, group7 }); + + Assert.AreSame(group1, group2); + Assert.AreSame(group1, group6); + Assert.AreSame(group1, group4); + Assert.AreSame(group1, group3); + Assert.AreSame(group1, group2); + Assert.AreSame(group1, group99); + Assert.AreNotSame(group1, group8); + Assert.AreSame(group8, group7); + + CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 6, 99 }, group1); + CollectionAssert.AreEqual(new[] { 7, 8 }, group8); + } + + [TestMethod] + public void Test_ObservableGroupedCollectionExtensions_InsertItem_WithComparer() + { + ObservableGroupedCollection groupedCollection = new(); + + ObservableGroup group1 = groupedCollection.InsertItem("A", Comparer.Default, 1, Comparer.Default); + ObservableGroup group2 = groupedCollection.InsertItem("A", Comparer.Default, 2, Comparer.Default); + ObservableGroup group6 = groupedCollection.InsertItem("A", Comparer.Default, 6, Comparer.Default); + ObservableGroup group4 = groupedCollection.InsertItem("A", Comparer.Default, 4, Comparer.Default); + ObservableGroup group3 = groupedCollection.InsertItem("A", Comparer.Default, 3, Comparer.Default); + ObservableGroup group99 = groupedCollection.InsertItem("A", Comparer.Default, 99, Comparer.Default); + ObservableGroup group8 = groupedCollection.InsertItem("B", Comparer.Default, 8, Comparer.Default); + ObservableGroup group7 = groupedCollection.InsertItem("B", Comparer.Default, 7, Comparer.Default); + + Assert.AreEqual(2, groupedCollection.Count); + CollectionAssert.AllItemsAreNotNull(new[] { group1, group2, group6, group4, group3, group99, group8, group7 }); + + Assert.AreSame(group1, group2); + Assert.AreSame(group1, group6); + Assert.AreSame(group1, group4); + Assert.AreSame(group1, group3); + Assert.AreSame(group1, group2); + Assert.AreSame(group1, group99); + Assert.AreNotSame(group1, group8); + Assert.AreSame(group8, group7); + + CollectionAssert.AreEqual(new[] { 1, 2, 3, 4, 6, 99 }, group1); + CollectionAssert.AreEqual(new[] { 7, 8 }, group8); + } + [TestMethod] public void Test_ObservableGroupedCollectionExtensions_RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing() { From ba20320cdc44ed619b88520a9616461dd6883579 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 6 Apr 2022 22:52:19 +0200 Subject: [PATCH 20/22] Add a comment explaining the collection fast paths --- .../Collections/ObservableGroupedCollectionExtensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs index d0a31f2ce..c1641f5f5 100644 --- a/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs +++ b/CommunityToolkit.Mvvm/Collections/ObservableGroupedCollectionExtensions.cs @@ -63,6 +63,12 @@ static void ThrowArgumentExceptionForKeyNotFound() ArgumentNullException.ThrowIfNull(source); ArgumentNullException.For.ThrowIfNull(key); + // This pattern is used extensively in this file, with many of the public APIs having a first loop on the retrieved + // list, and then a fallback one sometimes with the same logic, but on the collection itself. This is done as an + // optimization: if a list is available, we can iterate on it directly, which will use List.Enumerator and avoid + // allocations (the enumerator is a struct), additional indirections (the enumerator wraps the list instead of the + // outer collection, and additional overhead (using the value enumerator avoids the interface stub dispatches). + // Because of this, duplicate logic below is intentional and not actually duplicate, as it results in different code. if (source.TryGetList(out List>? list)) { foreach (ObservableGroup group in list) From 48deec9f77f9c6effba4317f42b8f8436da533f8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 6 Apr 2022 22:57:39 +0200 Subject: [PATCH 21/22] Remove TODOs --- .../Test_ReadOnlyObservableGroupedCollection.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs index a82e65353..549301282 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs @@ -197,11 +197,7 @@ public void Test_ReadOnlyObservableGroupedCollection_AddGroupInSource_ShouldAddG int expectedReadOnlyGroupCount = sourceInitialItemsCount + 1; Assert.AreEqual(readOnlyGroup.Count, expectedReadOnlyGroupCount); - - // TODO - //Assert.AreEqual(readOnlyGroup.Last().Key, "Add"); - //CollectionAssert.AreEquivalent(readOnlyGroup.Last(), itemsList); - + Assert.AreEqual("Add", readOnlyGroup[readOnlyGroup.Count - 1].Key); Assert.IsTrue(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); Assert.AreEqual(collectionChangedEventsCount, 1); @@ -237,11 +233,7 @@ public void Test_ReadOnlyObservableGroupedCollection_InsertGroupInSource_ShouldA source.Insert(insertionIndex, new ObservableGroup("Add", itemsList)); Assert.AreEqual(readOnlyGroup.Count, 3); - - // TODO - //Assert.AreEqual(readOnlyGroup.ElementAt(insertionIndex).Key, "Add"); - //CollectionAssert.AreEquivalent(readOnlyGroup.ElementAt(insertionIndex), itemsList); - + Assert.AreEqual("Add", readOnlyGroup[insertionIndex].Key); Assert.IsTrue(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs); Assert.AreEqual(collectionChangedEventsCount, 1); From b8979472ffb1e845c4e576afb5962434f838b179 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 6 Apr 2022 23:01:27 +0200 Subject: [PATCH 22/22] Add an extra test case --- ...est_ReadOnlyObservableGroupedCollection.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs index 549301282..21b52285c 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Collections/Test_ReadOnlyObservableGroupedCollection.cs @@ -284,17 +284,22 @@ public void Test_ReadOnlyObservableGroupedCollection_RemoveGroupInSource_ShoudRe [TestMethod] [DataRow(1, 0)] [DataRow(0, 1)] + [DataRow(0, 2)] public void Test_ReadOnlyObservableGroupedCollection_MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex) { NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null; int collectionChangedEventsCount = 0; bool isCountPropertyChangedEventRaised = false; int[] aItemsList = new[] { 1, 2, 3 }; - int[] bItemsList = new[] { 2, 4, 6 }; + int[] bItemsList = new[] { 4, 5, 6 }; + int[] cItemsList = new[] { 7, 8, 9 }; + int[] dItemsList = new[] { 10, 11, 12 }; List> groups = new() { new IntGroup("A", aItemsList), new IntGroup("B", bItemsList), + new IntGroup("C", cItemsList), + new IntGroup("D", dItemsList), }; ObservableGroupedCollection source = new(groups); ReadOnlyObservableGroupedCollection readOnlyGroup = new(source); @@ -307,13 +312,22 @@ public void Test_ReadOnlyObservableGroupedCollection_MoveGroupInSource_ShoudMove source.Move(oldIndex, newIndex); - Assert.AreEqual(readOnlyGroup.Count, 2); + Assert.AreEqual(groups.Count, readOnlyGroup.Count); Assert.AreEqual(readOnlyGroup[0].Key, "B"); CollectionAssert.AreEquivalent(readOnlyGroup[0], bItemsList); - Assert.AreEqual(readOnlyGroup[1].Key, "A"); - CollectionAssert.AreEquivalent(readOnlyGroup[1], aItemsList); + List> tempList = new(groups); + IGrouping tempItem = tempList[oldIndex]; + + tempList.RemoveAt(oldIndex); + tempList.Insert(newIndex, tempItem); + + for (int i = 0; i < tempList.Count; i++) + { + Assert.AreEqual(tempList[i].Key, readOnlyGroup[i].Key); + CollectionAssert.AreEqual(tempList[i].ToArray(), readOnlyGroup[i].ToArray()); + } Assert.IsFalse(isCountPropertyChangedEventRaised); Assert.IsNotNull(collectionChangedEventArgs);