Skip to content

Commit

Permalink
Remove from transform many with projection (#265)
Browse files Browse the repository at this point in the history
* Add failing unit tests

* Add an equality comparer in IChangeSet for lists

* Pass the equality comparer to the Clone method directly from TransformMany instead of using IChangeSet

* Add equality comparer to other calls to Clone method in TransformMany

* Bump minor version
  • Loading branch information
dchaib authored and RolandPheasant committed Aug 13, 2019
1 parent 2b19227 commit 0e1a9fe
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 9 deletions.
168 changes: 168 additions & 0 deletions src/DynamicData.Tests/List/TransformManyProjectionFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using DynamicData.Aggregation;
using DynamicData.Binding;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Xunit;

namespace DynamicData.Tests.List
{
public class TransformManyProjectionFixture : IDisposable
{
private readonly ISourceList<ClassWithNestedObservableCollection> _source;
private readonly IObservableList<ProjectedNestedChild> _results;

public TransformManyProjectionFixture()
{
_source = new SourceList<ClassWithNestedObservableCollection>();

_results = _source.Connect()
.AutoRefreshOnObservable(self => self.Children.ToObservableChangeSet())
.TransformMany(parent => parent.Children.Select(c => new ProjectedNestedChild(parent, c)), new ProjectNestedChildEqualityComparer())
.AsObservableList();
}

public void Dispose()
{
_source.Dispose();
}

[Fact]
public void AddRange()
{
var children = new[]
{
new NestedChild("A", "ValueA"),
new NestedChild("B", "ValueB"),
new NestedChild("C", "ValueC"),
new NestedChild("D", "ValueD"),
new NestedChild("E", "ValueE"),
new NestedChild("F", "ValueF")
};

var parents = new[]
{
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
new ClassWithNestedObservableCollection(3, new[] { children[4] })
};

_source.AddRange(parents);

_results.Count.Should().Be(5);
_results.Items.ShouldBeEquivalentTo(parents.SelectMany(p => p.Children.Take(5).Select(c => new ProjectedNestedChild(p, c))));
}

[Fact]
public void RemoveParent()
{
var children = new[]
{
new NestedChild("A", "ValueA"),
new NestedChild("B", "ValueB"),
new NestedChild("C", "ValueC"),
new NestedChild("D", "ValueD"),
new NestedChild("E", "ValueE"),
new NestedChild("F", "ValueF")
};

var parents = new[]
{
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
new ClassWithNestedObservableCollection(3, new[] { children[4] })
};

_source.AddRange(parents);

//remove a parent and check children have moved
_source.Remove(parents[0]);
_results.Count.Should().Be(3);
_results.Items.ShouldBeEquivalentTo(parents.Skip(1).SelectMany(p => p.Children.Select(c => new ProjectedNestedChild(p, c))));
}

[Fact]
public void RemoveChild()
{
var children = new[]
{
new NestedChild("A", "ValueA"),
new NestedChild("B", "ValueB"),
new NestedChild("C", "ValueC"),
new NestedChild("D", "ValueD"),
new NestedChild("E", "ValueE"),
new NestedChild("F", "ValueF")
};

var parents = new[]
{
new ClassWithNestedObservableCollection(1, new[] { children[0], children[1] }),
new ClassWithNestedObservableCollection(2, new[] { children[2], children[3] }),
new ClassWithNestedObservableCollection(3, new[] { children[4] })
};

_source.AddRange(parents);

//remove a child
parents[1].Children.Remove(children[3]);
_results.Count.Should().Be(4);
_results.Items.ShouldBeEquivalentTo(parents.SelectMany(p => p.Children.Where(child => child.Name != "D").Select(c => new ProjectedNestedChild(p, c))));
}

private class ProjectedNestedChild
{
public ClassWithNestedObservableCollection Parent { get; }

public NestedChild Child { get; }

public ProjectedNestedChild(ClassWithNestedObservableCollection parent, NestedChild child)
{
Parent = parent;
Child = child;
}
}

private class ProjectNestedChildEqualityComparer : IEqualityComparer<ProjectedNestedChild>
{
public bool Equals(ProjectedNestedChild x, ProjectedNestedChild y)
{
if (x == null || y == null)
return false;

return x.Child.Name == y.Child.Name;
}

public int GetHashCode(ProjectedNestedChild obj)
{
return obj.Child.Name.GetHashCode();
}
}

private class NestedChild
{
public string Name { get; }
public string Value { get; }

public NestedChild(string name, string value)
{
Name = name;
Value = value;
}
}

private class ClassWithNestedObservableCollection
{
public int Id { get; }
public ObservableCollection<NestedChild> Children { get; }

public ClassWithNestedObservableCollection(int id, IEnumerable<NestedChild> animals)
{
Id = id;
Children = new ObservableCollection<NestedChild>(animals);
}
}
}
}
23 changes: 19 additions & 4 deletions src/DynamicData/List/Internal/TransformMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,23 @@ public IObservable<IChangeSet<TDestination>> Run()
return CreateWithChangeset();
}

return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true)
.Select(changes => new ChangeSet<TDestination>(new DestinationEnumerator(changes, _equalityComparer))).NotEmpty();
return Observable.Create<IChangeSet<TDestination>>(observer =>
{
//NB: ChangeAwareList is used internally by dd to capture changes to a list and ensure they can be replayed by subsequent operators
var result = new ChangeAwareList<TDestination>();

return _source.Transform(item => new ManyContainer(_manyselector(item).ToArray()), true)
.Select(changes =>
{
var destinationChanges = new ChangeSet<TDestination>(new DestinationEnumerator(changes, _equalityComparer));
result.Clone(destinationChanges, _equalityComparer);
return result.CaptureChanges();
})

.NotEmpty()
.SubscribeSafe(observer);
}
);
}

private IObservable<IChangeSet<TDestination>> CreateWithChangeset()
Expand Down Expand Up @@ -123,15 +138,15 @@ private IObservable<IChangeSet<TDestination>> CreateWithChangeset()

var init = intial.Select(changes =>
{
result.Clone(changes);
result.Clone(changes, _equalityComparer);
return result.CaptureChanges();
});

var subseq = subsequent
.RemoveIndex()
.Select(changes =>
{
result.Clone(changes);
result.Clone(changes, _equalityComparer);
return result.CaptureChanges();
});

Expand Down
36 changes: 32 additions & 4 deletions src/DynamicData/List/ListEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ internal static bool MovedWithinRange<T>(this Change<T> source, int startIndex,
/// changes
/// </exception>
public static void Clone<T>(this IList<T> source, IChangeSet<T> changes)
{
Clone(source, changes, null);
}

/// <summary>
/// Clones the list from the specified change set
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="source">The source.</param>
/// <param name="changes">The changes.</param>
/// <param name="equalityComparer">An equality comparer to match items in the changes.</param>
/// <exception cref="System.ArgumentNullException">
/// source
/// or
/// changes
/// </exception>
public static void Clone<T>(this IList<T> source, IChangeSet<T> changes, IEqualityComparer<T> equalityComparer)
{
if (source == null)
{
Expand All @@ -58,11 +75,11 @@ public static void Clone<T>(this IList<T> source, IChangeSet<T> changes)

foreach (var item in changes)
{
Clone(source, item);
Clone(source, item, equalityComparer ?? EqualityComparer<T>.Default);
}
}

private static void Clone<T>(this IList<T> source, Change<T> item)
private static void Clone<T>(this IList<T> source, Change<T> item, IEqualityComparer<T> equalityComparer)
{
var changeAware = source as ChangeAwareList<T>;

Expand Down Expand Up @@ -154,8 +171,19 @@ private static void Clone<T>(this IList<T> source, Change<T> item)
source.RemoveAt(change.CurrentIndex);
}
else
{
source.Remove(change.Current);
{
if (equalityComparer != null)
{
int index = source.IndexOf(change.Current, equalityComparer);
if (index > -1)
{
source.RemoveAt(index);
}
}
else
{
source.Remove(change.Current);
}
}

break;
Expand Down
2 changes: 1 addition & 1 deletion version.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "6.12",
"version": "6.13",
"publicReleaseRefSpec": [
"^refs/heads/master$", // we release out of master
"^refs/heads/preview/.*", // we release previews
Expand Down

0 comments on commit 0e1a9fe

Please sign in to comment.