Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove some index -1 exceptions #781

Merged
merged 7 commits into from
Dec 5, 2023
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_prefer_collection_expression = true:suggestion
dotnet_diagnostic.CA2237.severity = none

[project.json]
indent_size = 2
Expand Down Expand Up @@ -699,6 +700,7 @@ csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_prefer_primary_constructors = true:suggestion
dotnet_diagnostic.RCS1194.severity = none
JakenVeina marked this conversation as resolved.
Show resolved Hide resolved

# C++ Files
[*.{cpp,h,in}]
Expand Down
311 changes: 311 additions & 0 deletions src/DynamicData.Tests/Binding/AvaloniaDictionaryFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

using DynamicData.Binding;
using DynamicData.Tests.Domain;
using FluentAssertions;
using Xunit;

namespace DynamicData.Tests.Binding;

public class AvaloniaDictionaryFixture
{
private readonly AvaloniaDictionary<string, Person> _collection;
private readonly ChangeSetAggregator<Person> _results;

public AvaloniaDictionaryFixture()
{
_collection = new AvaloniaDictionary<string, Person>();
_results = _collection.ToObservableChangeSet<AvaloniaDictionary<string, Person>, KeyValuePair<string, Person>>()
.Transform(x=>x.Value)
.AsAggregator();
}

[Fact]
public void Add()
{
var person = new Person("Someone",10, "M");

_collection.Add("Someone", person);

_results.Messages.Count.Should().Be(1);
_results.Data.Count.Should().Be(1);
_results.Data.Items.First().Should().Be(person);
}

[Fact]
public void Replace()
JakenVeina marked this conversation as resolved.
Show resolved Hide resolved
{
var person1 = new Person("Someone", 10, "M");
var person2 = new Person("Someone", 11, "M");

_collection.Add("Someone", person1);
_collection["Someone"] = person2;

_results.Data.Count.Should().Be(1);
_results.Data.Items.First().Should().Be(person2);
}


[Fact]
public void Remove()
{
var person = new Person("Someone", 10, "M");

_collection.Add("Someone", person);
_collection.Remove(person.Key);

_results.Data.Count.Should().Be(0);
}
}


public interface IAvaloniaDictionary<TKey, TValue>
: IDictionary<TKey, TValue>,
IAvaloniaReadOnlyDictionary<TKey, TValue>,
IDictionary
where TKey : notnull
{
}


public interface IAvaloniaReadOnlyDictionary<TKey, TValue>
: IReadOnlyDictionary<TKey, TValue>,
INotifyCollectionChanged,
INotifyPropertyChanged
where TKey : notnull
{
}


/*
Copied from Avalionia because an issue was raised due to compatibility issues with ToObservableChangeSet().

There's not other way of testing it.

See https://github.com/AvaloniaUI/Avalonia/blob/d7c82a1a6f7eb95b2214f20a281fa0581fb7b792/src/Avalonia.Base/Collections/AvaloniaDictionary.cs#L17
*/
public class AvaloniaDictionary<TKey, TValue> : IAvaloniaDictionary<TKey, TValue> where TKey : notnull
JakenVeina marked this conversation as resolved.
Show resolved Hide resolved
{
private Dictionary<TKey, TValue> _inner;

/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaDictionary{TKey, TValue}"/> class.
/// </summary>
public AvaloniaDictionary()
{
_inner = new Dictionary<TKey, TValue>();
}

/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaDictionary{TKey, TValue}"/> class.
/// </summary>
public AvaloniaDictionary(int capacity)
{
_inner = new Dictionary<TKey, TValue>(capacity);
}

/// <summary>
/// Occurs when the collection changes.
/// </summary>
public event NotifyCollectionChangedEventHandler? CollectionChanged;

/// <summary>
/// Raised when a property on the collection changes.
/// </summary>
public event PropertyChangedEventHandler? PropertyChanged;

/// <inheritdoc/>
public int Count => _inner.Count;

/// <inheritdoc/>
public bool IsReadOnly => false;

/// <inheritdoc/>
public ICollection<TKey> Keys => _inner.Keys;

/// <inheritdoc/>
public ICollection<TValue> Values => _inner.Values;

bool IDictionary.IsFixedSize => ((IDictionary)_inner).IsFixedSize;

ICollection IDictionary.Keys => ((IDictionary)_inner).Keys;

ICollection IDictionary.Values => ((IDictionary)_inner).Values;

bool ICollection.IsSynchronized => ((IDictionary)_inner).IsSynchronized;

object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;

IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => _inner.Keys;

IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => _inner.Values;

/// <summary>
/// Gets or sets the named resource.
/// </summary>
/// <param name="key">The resource key.</param>
/// <returns>The resource, or null if not found.</returns>
public TValue this[TKey key]
{
get
{
return _inner[key];
}

set
{
bool replace = _inner.TryGetValue(key, out var old);
_inner[key] = value;

if (replace)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]"));

if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Replace,
new KeyValuePair<TKey, TValue>(key, value),
new KeyValuePair<TKey, TValue>(key, old!));
CollectionChanged(this, e);
}
}
else
{
NotifyAdd(key, value);
}
}
}

object? IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; }

/// <inheritdoc/>
public void Add(TKey key, TValue value)
{
_inner.Add(key, value);
NotifyAdd(key, value);
}

/// <inheritdoc/>
public void Clear()
{
var old = _inner;

_inner = new Dictionary<TKey, TValue>();

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CommonPropertyNames.IndexerName));


if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
old.ToArray(),
-1);
CollectionChanged(this, e);
}
}

/// <inheritdoc/>
public bool ContainsKey(TKey key) => _inner.ContainsKey(key);

/// <inheritdoc/>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((IDictionary<TKey, TValue>)_inner).CopyTo(array, arrayIndex);
}

/// <inheritdoc/>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _inner.GetEnumerator();

/// <inheritdoc/>
public bool Remove(TKey key)
{
if (_inner.TryGetValue(key, out var value))
{
_inner.Remove(key);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]"));

if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove,
new[] { new KeyValuePair<TKey, TValue>(key, value) },
-1);
CollectionChanged(this, e);
}

return true;
}
else
{
return false;
}
}

/// <inheritdoc/>
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => _inner.TryGetValue(key, out value);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();

/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index) => ((ICollection)_inner).CopyTo(array, index);

/// <inheritdoc/>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}

/// <inheritdoc/>
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return _inner.Contains(item);
}

/// <inheritdoc/>
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}

/// <inheritdoc/>
void IDictionary.Add(object key, object? value) => Add((TKey)key, (TValue)value!);

/// <inheritdoc/>
bool IDictionary.Contains(object key) => ((IDictionary)_inner).Contains(key);

/// <inheritdoc/>
IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator();

/// <inheritdoc/>
void IDictionary.Remove(object key) => Remove((TKey)key);

private void NotifyAdd(TKey key, TValue value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]"));


if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add,
new[] { new KeyValuePair<TKey, TValue>(key, value) },
-1);
CollectionChanged(this, e);
}
}
}
internal static class CommonPropertyNames
{
public const string IndexerName = "Item";
}
24 changes: 24 additions & 0 deletions src/DynamicData.Tests/List/SourceListFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Xunit;

namespace DynamicData.Tests.List;

public class SourceListFixture
{
[Fact]
public void InitialChangeIsRange()
{
var source = new SourceList<string>();
source.Add("A");
var changeSets = new List<IChangeSet<string>>();

source.Connect().Subscribe(changeSets.Add).Dispose();


changeSets[0].First().Type.Should().Be(ChangeType.Range);
changeSets[0].First().Range.Index.Should().Be(0);
}
}
Loading