1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2025-04-10 11:15:45 +02:00

Merge pull request from CommunityToolkit/dev/improve-collections

Revamp observable collection APIs
This commit is contained in:
Sergio Pedri 2022-04-06 23:52:02 +02:00 committed by GitHub
commit f3bd3d8b72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1922 additions and 1264 deletions

View File

@ -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;
/// <summary>
/// An interface for a grouped collection of items.
/// </summary>
public interface IReadOnlyObservableGroup : INotifyPropertyChanged
public interface IReadOnlyObservableGroup : INotifyPropertyChanged, INotifyCollectionChanged, IEnumerable
{
/// <summary>
/// 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.
/// </summary>
int Count { get; }
/// <summary>
/// Gets the element at the specified index in the current collection.
/// </summary>
/// <param name="index">The zero-based index of the element to get.</param>
/// <returns>The element at the specified index in the read-only list.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is out of range.</exception>
object? this[int index] { get; }
}

View File

@ -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;
/// <summary>
/// An interface for a grouped collection of items.
/// </summary>
/// <typeparam name="TKey">The type of the group key.</typeparam>
/// <typeparam name="TElement">The type of elements in the group.</typeparam>
public interface IReadOnlyObservableGroup<out TKey, out TElement> : IReadOnlyObservableGroup<TKey>, IReadOnlyList<TElement>, IGrouping<TKey, TElement>
where TKey : notnull
{
/// <summary>
/// Gets the element at the specified index in the current collection.
/// </summary>
/// <param name="index">The zero-based index of the element to get.</param>
/// <returns>The element at the specified index in the read-only list.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if the index is out of range.</exception>
new TElement this[int index] { get; }
}

View File

@ -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;
/// <summary>
/// An interface for a grouped collection of items.
/// </summary>
/// <typeparam name="TKey">The type of the group key.</typeparam>
public interface IReadOnlyObservableGroup<out TKey> : IReadOnlyObservableGroup
where TKey : notnull
{
/// <summary>
/// Gets the key for the current collection.
/// </summary>
new TKey Key { get; }
}

View File

@ -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;
/// <summary>
/// A helper type for the <see cref="ObservableGroup{TKey, TValue}"/> type.
/// </summary>
internal static class ObservableGroupHelper
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="IReadOnlyObservableGroup.Key"/>
/// </summary>
public static readonly PropertyChangedEventArgs KeyChangedEventArgs = new(nameof(IReadOnlyObservableGroup.Key));
}

View File

@ -13,9 +13,9 @@ namespace CommunityToolkit.Mvvm.Collections;
/// <summary>
/// An observable list of observable groups.
/// </summary>
/// <typeparam name="TKey">The type of the group key.</typeparam>
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
public sealed class ObservableGroupedCollection<TKey, TValue> : ObservableCollection<ObservableGroup<TKey, TValue>>
/// <typeparam name="TKey">The type of the group keys.</typeparam>
/// <typeparam name="TElement">The type of elements in the collection.</typeparam>
public sealed class ObservableGroupedCollection<TKey, TElement> : ObservableCollection<ObservableGroup<TKey, TElement>>, ILookup<TKey, TElement>
where TKey : notnull
{
/// <summary>
@ -29,21 +29,50 @@ public ObservableGroupedCollection()
/// Initializes a new instance of the <see cref="ObservableGroupedCollection{TKey, TValue}"/> class.
/// </summary>
/// <param name="collection">The initial data to add in the grouped collection.</param>
public ObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
: base(collection.Select(static c => new ObservableGroup<TKey, TValue>(c)))
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="collection"/> is <see langword="null"/>.</exception>
public ObservableGroupedCollection(IEnumerable<IGrouping<TKey, TElement>> collection)
: base(collection?.Select(static group => new ObservableGroup<TKey, TElement>(group))!)
{
}
/// <inheritdoc/>
IEnumerable<TElement> ILookup<TKey, TElement>.this[TKey key]
{
get
{
IEnumerable<TElement>? result = null;
if (key is not null)
{
result = this.FirstGroupByKeyOrDefault(key);
}
return result ?? Enumerable.Empty<TElement>();
}
}
/// <summary>
/// Tries to get the underlying <see cref="List{T}"/> instance, if present.
/// </summary>
/// <param name="list">The resulting <see cref="List{T}"/>, if one was in use.</param>
/// <returns>Whether or not a <see cref="List{T}"/> instance has been found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool TryGetList([NotNullWhen(true)] out List<ObservableGroup<TKey, TValue>>? list)
internal bool TryGetList([NotNullWhen(true)] out List<ObservableGroup<TKey, TElement>>? list)
{
list = Items as List<ObservableGroup<TKey, TValue>>;
list = Items as List<ObservableGroup<TKey, TElement>>;
return list is not null;
}
/// <inheritdoc/>
bool ILookup<TKey, TElement>.Contains(TKey key)
{
return key is not null && this.FirstGroupByKey(key) is not null;
}
/// <inheritdoc/>
IEnumerator<IGrouping<TKey, TElement>> IEnumerable<IGrouping<TKey, TElement>>.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@ -4,9 +4,11 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using CommunityToolkit.Mvvm.Collections.Internals;
namespace CommunityToolkit.Mvvm.Collections;
@ -15,22 +17,20 @@ namespace CommunityToolkit.Mvvm.Collections;
/// It associates a <see cref="Key"/> to an <see cref="ObservableCollection{T}"/>.
/// </summary>
/// <typeparam name="TKey">The type of the group key.</typeparam>
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
/// <typeparam name="TElement">The type of elements in the group.</typeparam>
[DebuggerDisplay("Key = {Key}, Count = {Count}")]
public class ObservableGroup<TKey, TValue> : ObservableCollection<TValue>, IGrouping<TKey, TValue>, IReadOnlyObservableGroup
public sealed class ObservableGroup<TKey, TElement> : ObservableCollection<TElement>, IReadOnlyObservableGroup<TKey, TElement>
where TKey : notnull
{
/// <summary>
/// The cached <see cref="PropertyChangedEventArgs"/> for <see cref="Key"/>
/// </summary>
private static readonly PropertyChangedEventArgs KeyChangedEventArgs = new(nameof(Key));
/// <summary>
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
/// </summary>
/// <param name="key">The key for the group.</param>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="key"/> is <see langword="null"/>.</exception>
public ObservableGroup(TKey key)
{
ArgumentNullException.For<TKey>.ThrowIfNull(key);
this.key = key;
}
@ -38,7 +38,8 @@ public ObservableGroup(TKey key)
/// Initializes a new instance of the <see cref="ObservableGroup{TKey, TValue}"/> class.
/// </summary>
/// <param name="grouping">The grouping to fill the group.</param>
public ObservableGroup(IGrouping<TKey, TValue> grouping)
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="grouping"/> is <see langword="null"/>.</exception>
public ObservableGroup(IGrouping<TKey, TElement> grouping)
: base(grouping)
{
this.key = grouping.Key;
@ -49,9 +50,12 @@ public ObservableGroup(IGrouping<TKey, TValue> grouping)
/// </summary>
/// <param name="key">The key for the group.</param>
/// <param name="collection">The initial collection of data to add to the group.</param>
public ObservableGroup(TKey key, IEnumerable<TValue> collection)
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="key"/> or <paramref name="collection"/> are <see langword="null"/>.</exception>
public ObservableGroup(TKey key, IEnumerable<TElement> collection)
: base(collection)
{
ArgumentNullException.For<TKey>.ThrowIfNull(key);
this.key = key;
}
@ -60,20 +64,39 @@ public ObservableGroup(TKey key, IEnumerable<TValue> collection)
/// <summary>
/// Gets or sets the key of the group.
/// </summary>
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="value"/> is <see langword="null"/>.</exception>
public TKey Key
{
get => this.key;
set
{
ArgumentNullException.For<TKey>.ThrowIfNull(value);
if (!EqualityComparer<TKey>.Default.Equals(this.key!, value))
{
this.key = value;
OnPropertyChanged(KeyChangedEventArgs);
OnPropertyChanged(ObservableGroupHelper.KeyChangedEventArgs);
}
}
}
/// <summary>
/// Tries to get the underlying <see cref="List{T}"/> instance, if present.
/// </summary>
/// <param name="list">The resulting <see cref="List{T}"/>, if one was in use.</param>
/// <returns>Whether or not a <see cref="List{T}"/> instance has been found.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool TryGetList([NotNullWhen(true)] out List<TElement>? list)
{
list = Items as List<TElement>;
return list is not null;
}
/// <inheritdoc/>
object IReadOnlyObservableGroup.Key => Key;
/// <inheritdoc/>
object? IReadOnlyObservableGroup.this[int index] => this[index];
}

View File

@ -0,0 +1,191 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
namespace CommunityToolkit.Mvvm.Collections;
/// <summary>
/// A read-only list of groups.
/// </summary>
/// <typeparam name="TKey">The type of the group keys.</typeparam>
/// <typeparam name="TElement">The type of elements in the collection.</typeparam>
public sealed class ReadOnlyObservableGroupedCollection<TKey, TElement> : ReadOnlyObservableCollection<ReadOnlyObservableGroup<TKey, TElement>>, ILookup<TKey, TElement>
where TKey : notnull
{
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
/// </summary>
/// <param name="collection">The source collection to wrap.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="collection"/> is <see langword="null"/>.</exception>
public ReadOnlyObservableGroupedCollection(ObservableCollection<ObservableGroup<TKey, TElement>> collection)
: base(new ObservableCollection<ReadOnlyObservableGroup<TKey, TElement>>(collection?.Select(static g => new ReadOnlyObservableGroup<TKey, TElement>(g))!))
{
collection!.CollectionChanged += OnSourceCollectionChanged;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
/// </summary>
/// <param name="collection">The source collection to wrap.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="collection"/> is <see langword="null"/>.</exception>
public ReadOnlyObservableGroupedCollection(ObservableCollection<ReadOnlyObservableGroup<TKey, TElement>> collection)
: base(collection)
{
}
/// <inheritdoc/>
IEnumerable<TElement> ILookup<TKey, TElement>.this[TKey key]
{
get
{
IEnumerable<TElement>? result = null;
if (key is not null)
{
result = FirstGroupByKeyOrDefault(key);
}
return result ?? Enumerable.Empty<TElement>();
}
}
/// <inheritdoc/>
bool ILookup<TKey, TElement>.Contains(TKey key)
{
return key is not null && FirstGroupByKeyOrDefault(key) is not null;
}
/// <inheritdoc/>
IEnumerator<IGrouping<TKey, TElement>> IEnumerable<IGrouping<TKey, TElement>>.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// Forwards the <see cref="INotifyCollectionChanged.CollectionChanged"/> event whenever it is raised by the wrapped collection.
/// </summary>
/// <param name="sender">The wrapped collection (an <see cref="ObservableCollection{T}"/> of <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> instance).</param>
/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> arguments.</param>
/// <exception cref="NotSupportedException">Thrown if a range operation is requested.</exception>
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 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()
{
throw new NotSupportedException(
"ReadOnlyObservableGroupedCollection<TKey, TValue> 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<T> 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<ReadOnlyObservableGroup<TKey, TValue>>, so doing a direct cast here will always succeed
ObservableCollection<ReadOnlyObservableGroup<TKey, TElement>> items = (ObservableCollection<ReadOnlyObservableGroup<TKey, TElement>>)Items;
switch (e.Action)
{
// Insert a single item for an "Add" operation, fail if multiple items are added
case NotifyCollectionChangedAction.Add:
if (e.NewItems!.Count == 1)
{
ObservableGroup<TKey, TElement> newItem = (ObservableGroup<TKey, TElement>)e.NewItems![0]!;
items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TElement>(newItem));
}
else if (e.NewItems!.Count > 1)
{
ThrowNotSupportedExceptionForRangeOperation();
}
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.RemoveAt(e.OldStartingIndex);
}
else if (e.OldItems!.Count > 1)
{
ThrowNotSupportedExceptionForRangeOperation();
}
break;
// 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<TKey, TElement> replacedItem = (ObservableGroup<TKey, TElement>)e.NewItems![0]!;
items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TElement>(replacedItem);
}
else if (e.OldItems!.Count > 1 || e.NewItems!.Count > 1)
{
ThrowNotSupportedExceptionForRangeOperation();
}
break;
// 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();
break;
default:
break;
}
}
/// <summary>
/// Returns the first group with <paramref name="key"/> key or <see langword="null"/> if not found.
/// </summary>
/// <param name="key">The key of the group to query (assumed not to be <see langword="null"/>).</param>
/// <returns>The first group matching <paramref name="key"/>.</returns>
private IEnumerable<TElement>? FirstGroupByKeyOrDefault(TKey key)
{
if (Items is List<ReadOnlyObservableGroup<TKey, TElement>> list)
{
foreach (ReadOnlyObservableGroup<TKey, TElement> group in list)
{
if (EqualityComparer<TKey>.Default.Equals(group.Key, key))
{
return group;
}
}
return null;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static IEnumerable<TElement>? FirstGroupByKeyOrDefaultFallback(ReadOnlyObservableGroupedCollection<TKey, TElement> source, TKey key)
{
return Enumerable.FirstOrDefault<ReadOnlyObservableGroup<TKey, TElement>>(source, group => EqualityComparer<TKey>.Default.Equals(group.Key, key));
}
return FirstGroupByKeyOrDefaultFallback(this, key);
}
}

View File

@ -1,103 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
namespace CommunityToolkit.Mvvm.Collections;
/// <summary>
/// A read-only list of groups.
/// </summary>
/// <typeparam name="TKey">The type of the group key.</typeparam>
/// <typeparam name = "TValue" > The type of the items in the collection.</typeparam>
public sealed class ReadOnlyObservableGroupedCollection<TKey, TValue> : ReadOnlyObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>
where TKey : notnull
{
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
/// </summary>
/// <param name="collection">The source collection to wrap.</param>
public ReadOnlyObservableGroupedCollection(ObservableGroupedCollection<TKey, TValue> collection)
: this(collection.Select(static g => new ReadOnlyObservableGroup<TKey, TValue>(g)))
{
((INotifyCollectionChanged)collection).CollectionChanged += OnSourceCollectionChanged;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
/// </summary>
/// <param name="collection">The initial data to add in the grouped collection.</param>
public ReadOnlyObservableGroupedCollection(IEnumerable<ReadOnlyObservableGroup<TKey, TValue>> collection)
: base(new ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>(collection))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroupedCollection{TKey, TValue}"/> class.
/// </summary>
/// <param name="collection">The initial data to add in the grouped collection.</param>
public ReadOnlyObservableGroupedCollection(IEnumerable<IGrouping<TKey, TValue>> collection)
: this(collection.Select(static g => new ReadOnlyObservableGroup<TKey, TValue>(g.Key, g)))
{
}
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)
{
static void ThrowNotSupportedException()
{
throw new NotSupportedException(
"ReadOnlyObservableGroupedCollection<TKey, TValue> 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<T> 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();
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Replace:
// 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<TKey, TValue> newItem = e.NewItems!.Cast<ObservableGroup<TKey, TValue>>().First();
if (e.Action == NotifyCollectionChangedAction.Add)
{
Items.Insert(e.NewStartingIndex, new ReadOnlyObservableGroup<TKey, TValue>(newItem));
}
else
{
Items[e.OldStartingIndex] = new ReadOnlyObservableGroup<TKey, TValue>(newItem);
}
break;
case NotifyCollectionChangedAction.Move:
// Our inner Items list is our own ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>> so we can safely cast Items to its concrete type here.
((ObservableCollection<ReadOnlyObservableGroup<TKey, TValue>>)Items).Move(e.OldStartingIndex, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
Items.RemoveAt(e.OldStartingIndex);
break;
case NotifyCollectionChangedAction.Reset:
Items.Clear();
break;
default:
Debug.Fail("unsupported value");
break;
}
}
}

View File

@ -2,9 +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.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Diagnostics;
namespace CommunityToolkit.Mvvm.Collections;
@ -12,8 +11,9 @@ namespace CommunityToolkit.Mvvm.Collections;
/// A read-only observable group. It associates a <see cref="Key"/> to a <see cref="ReadOnlyObservableCollection{T}"/>.
/// </summary>
/// <typeparam name="TKey">The type of the group key.</typeparam>
/// <typeparam name="TValue">The type of the items in the collection.</typeparam>
public sealed class ReadOnlyObservableGroup<TKey, TValue> : ReadOnlyObservableCollection<TValue>, IGrouping<TKey, TValue>, IReadOnlyObservableGroup
/// <typeparam name="TElement">The type of elements in the group.</typeparam>
[DebuggerDisplay("Key = {Key}, Count = {Count}")]
public sealed class ReadOnlyObservableGroup<TKey, TElement> : ReadOnlyObservableCollection<TElement>, IReadOnlyObservableGroup<TKey, TElement>
where TKey : notnull
{
/// <summary>
@ -21,9 +21,12 @@ public sealed class ReadOnlyObservableGroup<TKey, TValue> : ReadOnlyObservableCo
/// </summary>
/// <param name="key">The key of the group.</param>
/// <param name="collection">The collection of items to add in the group.</param>
public ReadOnlyObservableGroup(TKey key, ObservableCollection<TValue> collection)
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="key"/> or <paramref name="collection"/> are <see langword="null"/>.</exception>
public ReadOnlyObservableGroup(TKey key, ObservableCollection<TElement> collection)
: base(collection)
{
ArgumentNullException.For<TKey>.ThrowIfNull(key);
Key = key;
}
@ -31,26 +34,19 @@ public ReadOnlyObservableGroup(TKey key, ObservableCollection<TValue> collection
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
/// </summary>
/// <param name="group">The <see cref="ObservableGroup{TKey, TValue}"/> to wrap.</param>
public ReadOnlyObservableGroup(ObservableGroup<TKey, TValue> group)
/// <exception cref="System.ArgumentNullException">Thrown if <paramref name="group"/> is <see langword="null"/>.</exception>
public ReadOnlyObservableGroup(ObservableGroup<TKey, TElement> group)
: base(group)
{
Key = group.Key;
}
/// <summary>
/// Initializes a new instance of the <see cref="ReadOnlyObservableGroup{TKey, TValue}"/> class.
/// </summary>
/// <param name="key">The key of the group.</param>
/// <param name="collection">The collection of items to add in the group.</param>
public ReadOnlyObservableGroup(TKey key, IEnumerable<TValue> collection)
: base(new ObservableCollection<TValue>(collection))
{
Key = key;
}
/// <inheritdoc/>
public TKey Key { get; }
/// <inheritdoc/>
object IReadOnlyObservableGroup.Key => Key;
/// <inheritdoc/>
object? IReadOnlyObservableGroup.this[int index] => this[index];
}

View File

@ -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)

View File

@ -7,13 +7,22 @@
namespace CommunityToolkit.Mvvm.UnitTests;
public class IntGroup : List<int>, IGrouping<string, int>
/// <summary>
/// A simple <see cref="IGrouping{TKey, TElement}"/> implementation for <see cref="string"/> and <see cref="int"/> values.
/// </summary>
internal sealed class IntGroup : List<int>, IGrouping<string, int>
{
/// <summary>
/// Creates a new <see cref="IntGroup"/> instance with the specified parameters.
/// </summary>
/// <param name="key">The group key.</param>
/// <param name="collection">The group values.</param>
public IntGroup(string key, IEnumerable<int> collection)
: base(collection)
{
Key = key;
}
/// <inheritdoc/>
public string Key { get; }
}

View File

@ -1,115 +0,0 @@
// 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.Specialized;
using System.Linq;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests;
[TestClass]
public class ObservableGroupTests
{
[TestMethod]
public void Ctor_ShouldHaveExpectedState()
{
ObservableGroup<string, int>? group = new("key");
Assert.AreEqual(group.Key, "key");
Assert.AreEqual(group.Count, 0);
}
[TestMethod]
public void Ctor_WithGrouping_ShouldHaveExpectedState()
{
IntGroup? source = new("key", new[] { 1, 2, 3 });
ObservableGroup<string, int>? group = new(source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Ctor_WithCollection_ShouldHaveExpectedState()
{
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Add_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
group.Add(4);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3, 4 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Update_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
group[1] = 4;
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 4, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Remove_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
_ = group.Remove(1);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 2, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Clear_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
group.Clear();
Assert.AreEqual(group.Key, "key");
Assert.AreEqual(group.Count, 0);
Assert.IsTrue(collectionChangedEventRaised);
}
[DataTestMethod]
[DataRow(0)]
[DataRow(3)]
public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count)
{
ObservableGroup<string, int>? group = new("key", Enumerable.Range(0, count));
IReadOnlyObservableGroup? iReadOnlyObservableGroup = (IReadOnlyObservableGroup)group;
Assert.AreEqual(iReadOnlyObservableGroup.Key, "key");
Assert.AreEqual(iReadOnlyObservableGroup.Count, count);
}
}

View File

@ -1,472 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests;
[TestClass]
public class ObservableGroupedCollectionExtensionsTests
{
[TestMethod]
public void First_WhenGroupExists_ShouldReturnFirstGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
ObservableGroup<string, int>? target = groupedCollection.AddGroup("B", 10);
_ = groupedCollection.AddGroup("B", 42);
ObservableGroup<string, int>? result = groupedCollection.First("B");
Assert.AreSame(result, target);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void First_WhenGroupDoesNotExist_ShouldThrow()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
_ = groupedCollection.First("I do not exist");
}
[TestMethod]
public void FirstOrDefault_WhenGroupExists_ShouldReturnFirstGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
ObservableGroup<string, int>? target = groupedCollection.AddGroup("B", 10);
_ = groupedCollection.AddGroup("B", 42);
ObservableGroup<string, int>? result = groupedCollection.FirstOrDefault("B");
Assert.AreSame(result, target);
}
[TestMethod]
public void FirstOrDefault_WhenGroupDoesNotExist_ShouldReturnNull()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
ObservableGroup<string, int>? result = groupedCollection.FirstOrDefault("I do not exist");
Assert.IsNull(result);
}
[TestMethod]
public void ElementAt_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
_ = groupedCollection.AddGroup("B", 10, 11, 12);
_ = groupedCollection.AddGroup("B", 42);
int result = groupedCollection.ElementAt("B", 2);
Assert.AreEqual(result, 12);
}
[DataTestMethod]
[DataRow(-1)]
[DataRow(3)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void ElementAt_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int index)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
_ = groupedCollection.AddGroup("B", 10, 11, 12);
_ = groupedCollection.AddGroup("B", 42);
_ = groupedCollection.ElementAt("B", index);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void ElementAt_WhenGroupDoesNotExist_ShouldThrow()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
_ = groupedCollection.ElementAt("I do not exist", 0);
}
[TestMethod]
public void ElementAtOrDefault_WhenGroupExistsAndIndexInRange_ShouldReturnValue()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 23);
_ = groupedCollection.AddGroup("B", 10, 11, 12);
_ = groupedCollection.AddGroup("B", 42);
int result = groupedCollection.ElementAt("B", 2);
Assert.AreEqual(result, 12);
}
[DataTestMethod]
[DataRow(-1)]
[DataRow(3)]
public void ElementAtOrDefault_WhenGroupExistsAndIndexOutOfRange_ShouldReturnDefaultValue(int index)
{
ObservableGroupedCollection<string, int>? 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<string, int>? 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<string, int>? groupedCollection = new();
ObservableGroup<string, int>? addedGroup = groupedCollection.AddGroup("new key", 23);
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void AddGroup_WithCollection_ShouldAddGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
ObservableGroup<string, int>? addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 });
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23, 10, 42 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void AddGroup_WithParamsCollection_ShouldAddGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
ObservableGroup<string, int>? addedGroup = groupedCollection.AddGroup("new key", 23, 10, 42);
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23, 10, 42 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void AddItem_WhenTargetGroupDoesNotExists_ShouldCreateAndAddNewGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
ObservableGroup<string, int>? addedGroup = groupedCollection.AddItem("new key", 23);
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
ObservableGroup<string, int>? targetGroup = groupedCollection.AddGroup("B", 4, 5, 6);
_ = groupedCollection.AddGroup("C", 7, 8);
ObservableGroup<string, int>? addedGroup = groupedCollection.AddItem("B", 23);
Assert.AreSame(addedGroup, targetGroup);
Assert.AreEqual(addedGroup.Key, "B");
CollectionAssert.AreEqual(addedGroup, new[] { 4, 5, 6, 23 });
Assert.AreEqual(groupedCollection.Count, 3);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(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.ElementAt(2).Key, "C");
CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 7, 8 });
}
[TestMethod]
public void AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExistingGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
ObservableGroup<string, int>? targetGroup = groupedCollection.AddGroup("B", 4, 5, 6);
_ = groupedCollection.AddGroup("B", 7, 8, 9);
_ = groupedCollection.AddGroup("C", 10, 11);
ObservableGroup<string, int>? addedGroup = groupedCollection.AddItem("B", 23);
Assert.AreSame(addedGroup, targetGroup);
Assert.AreEqual(addedGroup.Key, "B");
CollectionAssert.AreEqual(addedGroup, new[] { 4, 5, 6, 23 });
Assert.AreEqual(groupedCollection.Count, 4);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(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.ElementAt(2).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 7, 8, 9 });
Assert.AreEqual(groupedCollection.ElementAt(3).Key, "C");
CollectionAssert.AreEqual(groupedCollection.ElementAt(3), new[] { 10, 11 });
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void InsertItem_WhenGroupDoesNotExist_ShouldThrow()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.InsertItem("I do not exist", 0, 23);
}
[DataTestMethod]
[DataRow(-1)]
[DataRow(4)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void InsertItem_WhenIndexOutOfRange_ShouldThrow(int index)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.InsertItem("A", index, 23);
}
[DataTestMethod]
[DataRow(0, new[] { 23, 1, 2, 3 })]
[DataRow(1, new[] { 1, 23, 2, 3 })]
[DataRow(3, new[] { 1, 2, 3, 23 })]
public void InsertItem_WithValidIndex_WithSeveralGroups_ShoudInsertItemInFirstGroup(int index, int[] expecteGroupValues)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 4, 5);
ObservableGroup<string, int>? targetGroup = groupedCollection.AddGroup("B", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 6, 7);
ObservableGroup<string, int>? 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.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(1), expecteGroupValues);
Assert.AreEqual(groupedCollection.ElementAt(2).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 6, 7 });
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void SetItem_WhenGroupDoesNotExist_ShouldThrow()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.SetItem("I do not exist", 0, 23);
}
[DataTestMethod]
[DataRow(-1)]
[DataRow(3)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void SetItem_WhenIndexOutOfRange_ShouldThrow(int index)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.SetItem("A", index, 23);
}
[DataTestMethod]
[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<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 4, 5);
ObservableGroup<string, int>? targetGroup = groupedCollection.AddGroup("B", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 6, 7);
ObservableGroup<string, int>? group = groupedCollection.SetItem("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.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(1), expectedGroupValues);
Assert.AreEqual(groupedCollection.ElementAt(2).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(2), new[] { 6, 7 });
}
[TestMethod]
public void RemoveGroup_WhenGroupDoesNotExists_ShouldDoNothing()
{
ObservableGroupedCollection<string, int>? 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 });
}
[TestMethod]
public void RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup()
{
ObservableGroupedCollection<string, int>? 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 });
}
[TestMethod]
public void RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup()
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 4, 5, 6);
_ = groupedCollection.AddGroup("B", 7, 8);
groupedCollection.RemoveGroup("B");
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 7, 8 });
}
[DataTestMethod]
[DataRow(true)]
[DataRow(false)]
public void RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 4, 5, 6);
groupedCollection.RemoveItem("I do not exist", 8, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 5, 6 });
}
[DataTestMethod]
[DataRow(true)]
[DataRow(false)]
public void RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 4, 5, 6);
groupedCollection.RemoveItem("B", 8, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 5, 6 });
}
[DataTestMethod]
[DataRow(true)]
[DataRow(false)]
public void RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool removeGroupIfEmpty)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 4, 5, 6);
groupedCollection.RemoveItem("B", 5, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupedCollection.ElementAt(1), new[] { 4, 6 });
}
[DataTestMethod]
[DataRow(true, true)]
[DataRow(false, false)]
public void RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool removeGroupIfEmpty, bool expectGroupRemoved)
{
ObservableGroupedCollection<string, int>? groupedCollection = new();
_ = groupedCollection.AddGroup("A", 1, 2, 3);
_ = groupedCollection.AddGroup("B", 4);
groupedCollection.RemoveItem("B", 4, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, expectGroupRemoved ? 1 : 2);
Assert.AreEqual(groupedCollection.ElementAt(0).Key, "A");
CollectionAssert.AreEqual(groupedCollection.ElementAt(0), new[] { 1, 2, 3 });
if (!expectGroupRemoved)
{
Assert.AreEqual(groupedCollection.ElementAt(1).Key, "B");
Assert.AreEqual(groupedCollection.ElementAt(1).Count, 0);
}
}
}

View File

@ -1,41 +0,0 @@
// 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;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests;
[TestClass]
public class ObservableGroupedCollectionTests
{
[TestMethod]
public void Ctor_ShouldHaveExpectedValues()
{
ObservableGroupedCollection<string, int>? groupCollection = new();
Assert.AreEqual(groupCollection.Count, 0);
}
[TestMethod]
public void Ctor_WithGroups_ShouldHaveExpectedValues()
{
List<IGrouping<string, int>>? groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ObservableGroupedCollection<string, int>? 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.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(groupCollection.ElementAt(1), new[] { 2, 4, 6 });
}
}

View File

@ -1,128 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests;
[TestClass]
public class ReadOnlyObservableGroupTests
{
[TestMethod]
public void Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState()
{
ObservableCollection<int>? source = new(new[] { 1, 2, 3 });
ReadOnlyObservableGroup<string, int>? group = new("key", source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Ctor_ObservableGroup_ShouldHaveExpectedInitialState()
{
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int>? group = new(sourceGroup);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState()
{
int[]? source = new[] { 1, 2, 3 };
ReadOnlyObservableGroup<string, int>? group = new("key", source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Add_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int>? group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
sourceGroup.Add(4);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3, 4 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Update_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int>? group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
sourceGroup[1] = 4;
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 4, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Remove_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int>? group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
_ = sourceGroup.Remove(1);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 2, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Clear_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[]? source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int>? group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
sourceGroup.Clear();
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, Array.Empty<int>());
Assert.IsTrue(collectionChangedEventRaised);
}
[DataTestMethod]
[DataRow(0)]
[DataRow(3)]
public void IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count)
{
ObservableGroup<string, int>? sourceGroup = new("key", Enumerable.Range(0, count));
ReadOnlyObservableGroup<string, int>? group = new(sourceGroup);
IReadOnlyObservableGroup? iReadOnlyObservableGroup = (IReadOnlyObservableGroup)group;
Assert.AreEqual(iReadOnlyObservableGroup.Key, "key");
Assert.AreEqual(iReadOnlyObservableGroup.Count, count);
}
}

View File

@ -0,0 +1,156 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests.Collections;
[TestClass]
public class Test_ObservableGroup
{
[TestMethod]
public void Test_ObservableGroup_Ctor_ShouldHaveExpectedState()
{
ObservableGroup<string, int> group = new("key");
Assert.AreEqual(group.Key, "key");
Assert.AreEqual(group.Count, 0);
}
[TestMethod]
public void Test_ObservableGroup_Ctor_WithGrouping_ShouldHaveExpectedState()
{
IntGroup source = new("key", new[] { 1, 2, 3 });
ObservableGroup<string, int> group = new(source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ObservableGroup_Ctor_WithCollection_ShouldHaveExpectedState()
{
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> group = new("key", source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ObservableGroup_Add_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> group = new("key", source);
group.CollectionChanged += (s, e) => collectionChangedEventRaised = true;
group.Add(4);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3, 4 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Test_ObservableGroup_Update_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> group = new("key", source);
group.CollectionChanged += (s, e) => collectionChangedEventRaised = true;
group[1] = 4;
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 4, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Test_ObservableGroup_Remove_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
group.CollectionChanged += (s, e) => collectionChangedEventRaised = true;
_ = group.Remove(1);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 2, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Test_ObservableGroup_Clear_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int>? group = new("key", source);
group.CollectionChanged += (s, e) => collectionChangedEventRaised = true;
group.Clear();
Assert.AreEqual(group.Key, "key");
Assert.AreEqual(group.Count, 0);
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
[DataRow(0)]
[DataRow(3)]
public void Test_ObservableGroup_IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count)
{
ObservableGroup<string, int> group = new("key", Enumerable.Range(0, count));
IReadOnlyObservableGroup iReadOnlyObservableGroup = group;
Assert.AreEqual(iReadOnlyObservableGroup.Key, "key");
Assert.AreEqual(iReadOnlyObservableGroup.Count, count);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ObservableGroup_Ctor_NullKey()
{
_ = new ObservableGroup<string, int>((string)null!);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ObservableGroup_Ctor_NullGroup()
{
_ = new ObservableGroup<string, int>((IGrouping<string, int>)null!);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ObservableGroup_Ctor_NullKeyWithNotNullElements()
{
_ = new ObservableGroup<string, int>(null!, new int[0]);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ObservableGroup_Ctor_NotNullKeyWithNullElements()
{
_ = new ObservableGroup<string, int>("A", null!);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ObservableGroup_Ctor_NullKeySetter()
{
ObservableGroup<string, int> group = new("A");
group.Key = null!;
}
}

View File

@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests.Collections;
[TestClass]
public class Test_ObservableGroupedCollection
{
[TestMethod]
public void Test_ObservableGroupedCollection_Ctor_ShouldHaveExpectedValues()
{
ObservableGroupedCollection<string, int> groupCollection = new();
Assert.AreEqual(groupCollection.Count, 0);
}
[TestMethod]
public void Test_ObservableGroupedCollection_Ctor_WithGroups_ShouldHaveExpectedValues()
{
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ObservableGroupedCollection<string, int> groupCollection = new(groups);
Assert.AreEqual(groupCollection.Count, 2);
Assert.AreEqual(groupCollection[0].Key, "A");
CollectionAssert.AreEqual(groupCollection[0], new[] { 1, 3, 5 });
Assert.AreEqual(groupCollection[1].Key, "B");
CollectionAssert.AreEqual(groupCollection[1], new[] { 2, 4, 6 });
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ObservableGroupedCollection_Ctor_NullCollection()
{
_ = new ObservableGroupedCollection<string, int>(null!);
}
}

View File

@ -0,0 +1,454 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests.Collections;
[TestClass]
public class Test_ObservableGroupedCollectionExtensions
{
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExists_ShouldReturnFirstGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
ObservableGroup<string, int> target = groupedCollection.AddGroup("B", new[] { 10 });
_ = groupedCollection.AddGroup("B", new[] { 42 });
ObservableGroup<string, int> result = groupedCollection.FirstGroupByKey("B");
Assert.AreSame(result, target);
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupDoesNotExist_ShouldThrow()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
_ = groupedCollection.FirstGroupByKey("I do not exist");
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKeyOrDefault_WhenGroupExists_ShouldReturnFirstGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
ObservableGroup<string, int> target = groupedCollection.AddGroup("B", new[] { 10 });
_ = groupedCollection.AddGroup("B", new[] { 42 });
ObservableGroup<string, int>? result = groupedCollection.FirstGroupByKeyOrDefault("B");
Assert.AreSame(result, target);
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKeyOrDefault_WhenGroupDoesNotExist_ShouldReturnNull()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
ObservableGroup<string, int>? result = groupedCollection.FirstGroupByKeyOrDefault("I do not exist");
Assert.IsNull(result);
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExistsAndIndexInRange_ShouldReturnFirstGroupValue()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
_ = groupedCollection.AddGroup("B", new[] { 10, 11, 12 });
_ = groupedCollection.AddGroup("B", new[] { 42 });
int result = groupedCollection.FirstGroupByKey("B")[2];
Assert.AreEqual(result, 12);
}
[TestMethod]
[DataRow(-1)]
[DataRow(3)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExistsAndIndexOutOfRange_ShouldReturnThrow(int index)
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
_ = groupedCollection.AddGroup("B", new[] { 10, 11, 12 });
_ = groupedCollection.AddGroup("B", new[] { 42 });
_ = groupedCollection.FirstGroupByKey("B")[index];
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_FirstGroupByKey_WhenGroupExistsAndIndexInRange_ShouldReturnValue()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 23 });
_ = groupedCollection.AddGroup("B", new[] { 10, 11, 12 });
_ = groupedCollection.AddGroup("B", new[] { 42 });
int result = groupedCollection.FirstGroupByKey("B")[2];
Assert.AreEqual(result, 12);
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithItem_ShouldAddGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
ObservableGroup<string, int> addedGroup = groupedCollection.AddGroup("new key", new[] { 23 });
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithCollection_ShouldAddGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
ObservableGroup<string, int> addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 });
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23, 10, 42 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_AddGroup_WithParamsCollection_ShouldAddGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
ObservableGroup<string, int> addedGroup = groupedCollection.AddGroup("new key", new[] { 23, 10, 42 });
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23, 10, 42 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_InsertGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
ObservableGroup<string, int> a = groupedCollection.InsertGroup("A", new[] { 1, 2, 3 });
ObservableGroup<string, int> d = groupedCollection.InsertGroup("D", new[] { 1, 2, 3 });
ObservableGroup<string, int> e = groupedCollection.InsertGroup("E", new[] { 1, 2, 3 });
ObservableGroup<string, int> b = groupedCollection.InsertGroup("B", new[] { 1, 2, 3 });
ObservableGroup<string, int> z = groupedCollection.InsertGroup("Z", new[] { 1, 2, 3 });
ObservableGroup<string, int> 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<string, int> groupedCollection = new();
ObservableGroup<string, int> a = groupedCollection.InsertGroup(new IntGroup("A", new[] { 1, 2, 3 }));
ObservableGroup<string, int> d = groupedCollection.InsertGroup(new IntGroup("D", new[] { 1, 2, 3 }));
ObservableGroup<string, int> e = groupedCollection.InsertGroup(new IntGroup("E", new[] { 1, 2, 3 }));
ObservableGroup<string, int> b = groupedCollection.InsertGroup(new IntGroup("B", new[] { 1, 2, 3 }));
ObservableGroup<string, int> z = groupedCollection.InsertGroup(new IntGroup("Z", new[] { 1, 2, 3 }));
ObservableGroup<string, int> 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()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
ObservableGroup<string, int> addedGroup = groupedCollection.AddItem("new key", 23);
Assert.IsNotNull(addedGroup);
Assert.AreEqual(addedGroup.Key, "new key");
CollectionAssert.AreEqual(addedGroup, new[] { 23 });
CollectionAssert.AreEqual(groupedCollection, new[] { addedGroup });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenSingleTargetGroupAlreadyExists_ShouldAddItemToExistingGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
ObservableGroup<string, int> targetGroup = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
_ = groupedCollection.AddGroup("C", new[] { 7, 8 });
ObservableGroup<string, int> addedGroup = groupedCollection.AddItem("B", 23);
Assert.AreSame(addedGroup, targetGroup);
Assert.AreEqual(addedGroup.Key, "B");
CollectionAssert.AreEqual(addedGroup, new[] { 4, 5, 6, 23 });
Assert.AreEqual(groupedCollection.Count, 3);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection[1].Key, "B");
CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6, 23 });
Assert.AreEqual(groupedCollection[2].Key, "C");
CollectionAssert.AreEqual(groupedCollection[2], new[] { 7, 8 });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_AddItem_WhenSeveralTargetGroupsAlreadyExist_ShouldAddItemToFirstExistingGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
ObservableGroup<string, int> targetGroup = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
_ = groupedCollection.AddGroup("B", new[] { 7, 8, 9 });
_ = groupedCollection.AddGroup("C", new[] { 10, 11 });
ObservableGroup<string, int> addedGroup = groupedCollection.AddItem("B", 23);
Assert.AreSame(addedGroup, targetGroup);
Assert.AreEqual(addedGroup.Key, "B");
CollectionAssert.AreEqual(addedGroup, new[] { 4, 5, 6, 23 });
Assert.AreEqual(groupedCollection.Count, 4);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection[1].Key, "B");
CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6, 23 });
Assert.AreEqual(groupedCollection[2].Key, "B");
CollectionAssert.AreEqual(groupedCollection[2], new[] { 7, 8, 9 });
Assert.AreEqual(groupedCollection[3].Key, "C");
CollectionAssert.AreEqual(groupedCollection[3], new[] { 10, 11 });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_InsertItem()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
ObservableGroup<string, int> group1 = groupedCollection.InsertItem("A", 1);
ObservableGroup<string, int> group2 = groupedCollection.InsertItem("A", 2);
ObservableGroup<string, int> group6 = groupedCollection.InsertItem("A", 6);
ObservableGroup<string, int> group4 = groupedCollection.InsertItem("A", 4);
ObservableGroup<string, int> group3 = groupedCollection.InsertItem("A", 3);
ObservableGroup<string, int> group99 = groupedCollection.InsertItem("A", 99);
ObservableGroup<string, int> group8 = groupedCollection.InsertItem("B", 8);
ObservableGroup<string, int> 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<string, int> groupedCollection = new();
ObservableGroup<string, int> group1 = groupedCollection.InsertItem("A", Comparer<string>.Default, 1, Comparer<int>.Default);
ObservableGroup<string, int> group2 = groupedCollection.InsertItem("A", Comparer<string>.Default, 2, Comparer<int>.Default);
ObservableGroup<string, int> group6 = groupedCollection.InsertItem("A", Comparer<string>.Default, 6, Comparer<int>.Default);
ObservableGroup<string, int> group4 = groupedCollection.InsertItem("A", Comparer<string>.Default, 4, Comparer<int>.Default);
ObservableGroup<string, int> group3 = groupedCollection.InsertItem("A", Comparer<string>.Default, 3, Comparer<int>.Default);
ObservableGroup<string, int> group99 = groupedCollection.InsertItem("A", Comparer<string>.Default, 99, Comparer<int>.Default);
ObservableGroup<string, int> group8 = groupedCollection.InsertItem("B", Comparer<string>.Default, 8, Comparer<int>.Default);
ObservableGroup<string, int> group7 = groupedCollection.InsertItem("B", Comparer<string>.Default, 7, Comparer<int>.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()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
groupedCollection.RemoveGroup("I do not exist");
Assert.AreEqual(groupedCollection.Count, 1);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_RemoveGroup_WhenSingleGroupExists_ShouldRemoveGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
_ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
groupedCollection.RemoveGroup("B");
Assert.AreEqual(groupedCollection.Count, 1);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ObservableGroupedCollectionExtensions_RemoveGroup_WhenSeveralGroupsExist_ShouldRemoveFirstGroup()
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
_ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
_ = groupedCollection.AddGroup("B", new[] { 7, 8 });
groupedCollection.RemoveGroup("B");
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection[1].Key, "B");
CollectionAssert.AreEqual(groupedCollection[1], new[] { 7, 8 });
}
[TestMethod]
[DataRow(true)]
[DataRow(false)]
public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenGroupDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty)
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
_ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
groupedCollection.RemoveItem("I do not exist", 8, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection[1].Key, "B");
CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6 });
}
[TestMethod]
[DataRow(true)]
[DataRow(false)]
public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenGroupExistsAndItemDoesNotExist_ShouldDoNothing(bool removeGroupIfEmpty)
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
_ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
groupedCollection.RemoveItem("B", 8, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection[1].Key, "B");
CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 5, 6 });
}
[TestMethod]
[DataRow(true)]
[DataRow(false)]
public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenGroupAndItemExist_ShouldRemoveItemFromGroup(bool removeGroupIfEmpty)
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
_ = groupedCollection.AddGroup("B", new[] { 4, 5, 6 });
groupedCollection.RemoveItem("B", 5, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, 2);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
Assert.AreEqual(groupedCollection[1].Key, "B");
CollectionAssert.AreEqual(groupedCollection[1], new[] { 4, 6 });
}
[TestMethod]
[DataRow(true, true)]
[DataRow(false, false)]
public void Test_ObservableGroupedCollectionExtensions_RemoveItem_WhenRemovingLastItem_ShouldRemoveGroupIfRequired(bool removeGroupIfEmpty, bool expectGroupRemoved)
{
ObservableGroupedCollection<string, int> groupedCollection = new();
_ = groupedCollection.AddGroup("A", new[] { 1, 2, 3 });
_ = groupedCollection.AddGroup("B", new[] { 4 });
groupedCollection.RemoveItem("B", 4, removeGroupIfEmpty);
Assert.AreEqual(groupedCollection.Count, expectGroupRemoved ? 1 : 2);
Assert.AreEqual(groupedCollection[0].Key, "A");
CollectionAssert.AreEqual(groupedCollection[0], new[] { 1, 2, 3 });
if (!expectGroupRemoved)
{
Assert.AreEqual(groupedCollection[1].Key, "B");
Assert.AreEqual(groupedCollection[1].Count, 0);
}
}
}

View File

@ -0,0 +1,153 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using CommunityToolkit.Mvvm.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.UnitTests.Collections;
[TestClass]
public class Test_ReadOnlyObservableGroup
{
[TestMethod]
public void Test_ReadOnlyObservableGroup_Ctor_WithKeyAndOBservableCollection_ShouldHaveExpectedInitialState()
{
ObservableCollection<int> source = new(new[] { 1, 2, 3 });
ReadOnlyObservableGroup<string, int> group = new("key", source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ReadOnlyObservableGroup_Ctor_ObservableGroup_ShouldHaveExpectedInitialState()
{
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int> group = new(sourceGroup);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ReadOnlyObservableGroup_Ctor_WithKeyAndCollection_ShouldHaveExpectedInitialState()
{
ObservableCollection<int> source = new() { 1, 2, 3 };
ReadOnlyObservableGroup<string, int> group = new("key", source);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3 });
}
[TestMethod]
public void Test_ReadOnlyObservableGroup_Add_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int> group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
sourceGroup.Add(4);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 2, 3, 4 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Test_ReadOnlyObservableGroup_Update_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int> group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
sourceGroup[1] = 4;
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 1, 4, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Test_ReadOnlyObservableGroup_Remove_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int> group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
_ = sourceGroup.Remove(1);
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, new[] { 2, 3 });
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
public void Test_ReadOnlyObservableGroup_Clear_ShouldRaiseEvent()
{
bool collectionChangedEventRaised = false;
int[] source = new[] { 1, 2, 3 };
ObservableGroup<string, int> sourceGroup = new("key", source);
ReadOnlyObservableGroup<string, int> group = new(sourceGroup);
((INotifyCollectionChanged)group).CollectionChanged += (s, e) => collectionChangedEventRaised = true;
sourceGroup.Clear();
Assert.AreEqual(group.Key, "key");
CollectionAssert.AreEqual(group, Array.Empty<int>());
Assert.IsTrue(collectionChangedEventRaised);
}
[TestMethod]
[DataRow(0)]
[DataRow(3)]
public void Test_ReadOnlyObservableGroup_IReadOnlyObservableGroup_ShouldReturnExpectedValues(int count)
{
ObservableGroup<string, int> sourceGroup = new("key", Enumerable.Range(0, count));
ReadOnlyObservableGroup<string, int> group = new(sourceGroup);
IReadOnlyObservableGroup iReadOnlyObservableGroup = group;
Assert.AreEqual(iReadOnlyObservableGroup.Key, "key");
Assert.AreEqual(iReadOnlyObservableGroup.Count, count);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ReadOnlyObservableGroup_Ctor_NullKeyWithNotNullElements()
{
_ = new ReadOnlyObservableGroup<string, int>(null!, new ObservableCollection<int>());
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ReadOnlyObservableGroup_Ctor_NotNullKeyWithNullElements()
{
_ = new ReadOnlyObservableGroup<string, int>("A", null!);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ReadOnlyObservableGroup_Ctor_NullGroup()
{
_ = new ReadOnlyObservableGroup<string, int>(null!);
}
}

View File

@ -12,99 +12,80 @@
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()
public void Test_ReadOnlyObservableGroupedCollection_Ctor_WithEmptySource_ShoudInitializeObject()
{
ObservableGroupedCollection<string, int>? source = new();
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ObservableGroupedCollection<string, int> source = new();
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
Assert.AreEqual(readOnlyGroup.Count, 0);
CollectionAssert.AreEqual(readOnlyGroup, Array.Empty<int>());
}
[TestMethod]
public void Ctor_WithObservableGroupedCollection_ShoudInitializeObject()
public void Test_ReadOnlyObservableGroupedCollection_Ctor_WithObservableGroupedCollection_ShoudInitializeObject()
{
List<IGrouping<string, int>>? groups = new()
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> 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_WithListOfIGroupingSource_ShoudInitializeObject()
public void Test_ReadOnlyObservableGroupedCollection_Ctor_WithListOfReadOnlyObservableGroupSource_ShoudInitializeObject()
{
List<IGrouping<string, int>>? source = new()
ObservableCollection<ReadOnlyObservableGroup<string, int>> source = new()
{
new ReadOnlyObservableGroup<string, int>("A", new ObservableCollection<int> { 1, 3, 5 }),
new ReadOnlyObservableGroup<string, int>("B", new ObservableCollection<int> { 2, 4, 6 }),
};
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
Assert.AreEqual(readOnlyGroup.Count, 2);
Assert.AreEqual(readOnlyGroup[0].Key, "A");
CollectionAssert.AreEqual(readOnlyGroup[0], new[] { 1, 3, 5 });
Assert.AreEqual(readOnlyGroup[1].Key, "B");
CollectionAssert.AreEqual(readOnlyGroup[1], new[] { 2, 4, 6 });
}
[TestMethod]
public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_Properties_ShoudReturnExpectedValues()
{
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ReadOnlyObservableGroupedCollection<string, int>? 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<ReadOnlyObservableGroup<string, int>>? source = new()
{
new ReadOnlyObservableGroup<string, int>("A", new[] { 1, 3, 5 }),
new ReadOnlyObservableGroup<string, int>("B", new[] { 2, 4, 6 }),
};
ReadOnlyObservableGroupedCollection<string, int>? 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.ElementAt(1).Key, "B");
CollectionAssert.AreEqual(readOnlyGroup.ElementAt(1), new[] { 2, 4, 6 });
}
[TestMethod]
public void IListImplementation_Properties_ShoudReturnExpectedValues()
{
List<IGrouping<string, int>>? groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
IList? list = (IList)readOnlyGroup;
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
IList list = readOnlyGroup;
Assert.AreEqual(list.Count, 2);
ReadOnlyObservableGroup<string, int>? group0 = (ReadOnlyObservableGroup<string, int>)list[0]!;
ReadOnlyObservableGroup<string, int> group0 = (ReadOnlyObservableGroup<string, int>)list[0]!;
Assert.AreEqual(group0.Key, "A");
CollectionAssert.AreEqual(group0, new[] { 1, 3, 5 });
ReadOnlyObservableGroup<string, int>? group1 = (ReadOnlyObservableGroup<string, int>)list[1]!;
ReadOnlyObservableGroup<string, int> group1 = (ReadOnlyObservableGroup<string, int>)list[1]!;
Assert.AreEqual(group1.Key, "B");
CollectionAssert.AreEqual(group1, new[] { 2, 4, 6 });
@ -116,16 +97,16 @@ public void IListImplementation_Properties_ShoudReturnExpectedValues()
}
[TestMethod]
public void IListImplementation_MutableMethods_ShoudThrow()
public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_MutableMethods_ShoudThrow()
{
List<IGrouping<string, int>>? groups = new()
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
IList? list = (IList)readOnlyGroup;
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
IList list = readOnlyGroup;
ReadOnlyObservableGroup<string, int>? testGroup = new("test", new ObservableCollection<int>());
@ -142,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)
public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_IndexOf_ShoudReturnExpectedValue(int groupIndex)
{
List<IGrouping<string, int>>? groups = new()
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
new IntGroup("C", new[] { 7, 8, 9 }),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
IList? list = (IList)readOnlyGroup;
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
IList list = readOnlyGroup;
object? groupToSearch = groupIndex >= 0 ? list[groupIndex] : null;
@ -166,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)
public void Test_ReadOnlyObservableGroupedCollection_IListImplementation_Contains_ShoudReturnExpectedValue(int groupIndex, bool expectedResult)
{
List<IGrouping<string, int>>? groups = new()
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", new[] { 1, 3, 5 }),
new IntGroup("B", new[] { 2, 4, 6 }),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
IList? list = (IList)readOnlyGroup;
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
IList list = readOnlyGroup;
object? groupToSearch = groupIndex >= 0 ? list[groupIndex] : null;
@ -188,22 +169,22 @@ 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)
public void Test_ReadOnlyObservableGroupedCollection_AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int expectedInsertionIndex)
{
NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null;
int collectionChangedEventsCount = 0;
bool isCountPropertyChangedEventRaised = false;
int[]? itemsList = new[] { 1, 2, 3 };
ObservableGroupedCollection<string, int>? source = new();
int[] itemsList = new[] { 1, 2, 3 };
ObservableGroupedCollection<string, int> source = new();
for (int i = 0; i < sourceInitialItemsCount; i++)
{
source.Add(new ObservableGroup<string, int>($"group {i}", Enumerable.Empty<int>()));
}
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) =>
{
collectionChangedEventArgs = e;
@ -216,10 +197,7 @@ public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int exp
int expectedReadOnlyGroupCount = sourceInitialItemsCount + 1;
Assert.AreEqual(readOnlyGroup.Count, expectedReadOnlyGroupCount);
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);
@ -229,22 +207,22 @@ public void AddGroupInSource_ShouldAddGroup(int sourceInitialItemsCount, int exp
Assert.IsTrue(isAddEventValid);
}
[DataTestMethod]
[TestMethod]
[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;
bool isCountPropertyChangedEventRaised = false;
int[]? itemsList = new[] { 1, 2, 3 };
ObservableGroupedCollection<string, int>? source = new()
int[] itemsList = new[] { 1, 2, 3 };
ObservableGroupedCollection<string, int> source = new()
{
new ObservableGroup<string, int>("Group0", new[] { 10, 20, 30 }),
new ObservableGroup<string, int>("Group1", new[] { 40, 50, 60 })
};
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) =>
{
collectionChangedEventArgs = e;
@ -255,10 +233,7 @@ public void InsertGroupInSource_ShouldAddGroup(int insertionIndex)
source.Insert(insertionIndex, new ObservableGroup<string, int>("Add", itemsList));
Assert.AreEqual(readOnlyGroup.Count, 3);
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);
@ -269,20 +244,20 @@ public void InsertGroupInSource_ShouldAddGroup(int insertionIndex)
}
[TestMethod]
public void RemoveGroupInSource_ShoudRemoveGroup()
public void Test_ReadOnlyObservableGroupedCollection_RemoveGroupInSource_ShoudRemoveGroup()
{
NotifyCollectionChangedEventArgs? collectionChangedEventArgs = null;
int collectionChangedEventsCount = 0;
bool isCountPropertyChangedEventRaised = false;
int[]? aItemsList = new[] { 1, 2, 3 };
int[]? bItemsList = new[] { 2, 4, 6 };
List<IGrouping<string, int>>? groups = new()
int[] aItemsList = new[] { 1, 2, 3 };
int[] bItemsList = new[] { 2, 4, 6 };
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", aItemsList),
new IntGroup("B", bItemsList),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) =>
{
collectionChangedEventArgs = e;
@ -294,8 +269,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);
@ -306,23 +281,28 @@ public void RemoveGroupInSource_ShoudRemoveGroup()
Assert.IsTrue(isRemoveEventValid);
}
[DataTestMethod]
[TestMethod]
[DataRow(1, 0)]
[DataRow(0, 1)]
public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex)
[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 };
List<IGrouping<string, int>>? groups = new()
int[] aItemsList = new[] { 1, 2, 3 };
int[] bItemsList = new[] { 4, 5, 6 };
int[] cItemsList = new[] { 7, 8, 9 };
int[] dItemsList = new[] { 10, 11, 12 };
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", aItemsList),
new IntGroup("B", bItemsList),
new IntGroup("C", cItemsList),
new IntGroup("D", dItemsList),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) =>
{
collectionChangedEventArgs = e;
@ -332,13 +312,22 @@ public void MoveGroupInSource_ShoudMoveGroup(int oldIndex, int newIndex)
source.Move(oldIndex, newIndex);
Assert.AreEqual(readOnlyGroup.Count, 2);
Assert.AreEqual(groups.Count, readOnlyGroup.Count);
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);
List<IGrouping<string, int>> tempList = new(groups);
IGrouping<string, int> 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);
@ -350,20 +339,20 @@ 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;
bool isCountPropertyChangedEventRaised = false;
int[]? aItemsList = new[] { 1, 2, 3 };
int[]? bItemsList = new[] { 2, 4, 6 };
List<IGrouping<string, int>>? groups = new()
int[] aItemsList = new[] { 1, 2, 3 };
int[] bItemsList = new[] { 2, 4, 6 };
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", aItemsList),
new IntGroup("B", bItemsList),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) =>
{
collectionChangedEventArgs = e;
@ -385,21 +374,21 @@ public void ClearSource_ShoudClear()
}
[TestMethod]
public void ReplaceGroupInSource_ShoudReplaceGroup()
public void Test_ReadOnlyObservableGroupedCollection_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<IGrouping<string, int>>? groups = new()
int[] aItemsList = new[] { 1, 2, 3 };
int[] bItemsList = new[] { 2, 4, 6 };
int[] cItemsList = new[] { 7, 8, 9 };
List<IGrouping<string, int>> groups = new()
{
new IntGroup("A", aItemsList),
new IntGroup("B", bItemsList),
};
ObservableGroupedCollection<string, int>? source = new(groups);
ReadOnlyObservableGroupedCollection<string, int>? readOnlyGroup = new(source);
ObservableGroupedCollection<string, int> source = new(groups);
ReadOnlyObservableGroupedCollection<string, int> readOnlyGroup = new(source);
((INotifyCollectionChanged)readOnlyGroup).CollectionChanged += (s, e) =>
{
collectionChangedEventArgs = e;
@ -411,11 +400,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);
@ -479,4 +468,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 Test_ReadOnlyObservableGroupedCollection_Ctor_NullCollectionWithObservableGroups()
{
_ = new ReadOnlyObservableGroupedCollection<string, int>((ObservableCollection<ObservableGroup<string, int>>)null!);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_ReadOnlyObservableGroupedCollection_Ctor_NullCollectionWithReadOnlyObservableGroups()
{
_ = new ReadOnlyObservableGroupedCollection<string, int>((ObservableCollection<ReadOnlyObservableGroup<string, int>>)null!);
}
}