Skip to content

Commit

Permalink
Fixed handling of refresh and replace changes in DynamicCombiner, add…
Browse files Browse the repository at this point in the history
…ed tests (#253)
  • Loading branch information
Wouterdek authored and RolandPheasant committed Jul 16, 2019
1 parent 69a7e14 commit 8db149a
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 28 deletions.
48 changes: 48 additions & 0 deletions src/DynamicData.Tests/List/DynamicOrFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@

namespace DynamicData.Tests.List
{
public class DynamicOrRefreshFixture
{
[Fact]
public void RefreshPassesThrough()
{
var source1 = new SourceList<Item>();
var source2 = new SourceList<Item>();
var source = new SourceList<IObservable<IChangeSet<Item>>>();
var results = source.Or().AsAggregator();

source1.Add(new Item("A"));
source2.Add(new Item("B"));
source.AddRange(new[] { source1.Connect().AutoRefresh(), source2.Connect().AutoRefresh() });

source1.Items.ElementAt(0).Name = "Test";

results.Data.Count.Should().Be(2);
results.Messages.Count.Should().Be(3);
results.Messages[2].Refreshes.Should().Be(1);
results.Messages[2].First().Item.Current.Should().Be(source1.Items.First());
}
}

public class DynamicOrFixture: IDisposable
{
Expand Down Expand Up @@ -33,6 +55,32 @@ public void Dispose()
_results.Dispose();
}

[Fact]
public void ItemIsReplaced()
{
_source1.Add(0);
_source2.Add(1);
_source.Add(_source1.Connect());
_source.Add(_source2.Connect());
_source1.ReplaceAt(0, 9);

_results.Data.Count.Should().Be(2);
_results.Messages.Count.Should().Be(3);
_results.Data.Items.Should().BeEquivalentTo(9, 1);
}

[Fact]
public void ClearSource()
{
_source1.Add(0);
_source2.Add(1);
_source.Add(_source1.Connect());
_source.Add(_source2.Connect());
_source.Clear();

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

[Fact]
public void IncludedWhenItemIsInOneSource()
{
Expand Down
2 changes: 1 addition & 1 deletion src/DynamicData.Tests/List/DynamicXOrFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public void ClearOnlyClearsOneSource()
}

[Fact]
public void OverlappingRangeExludesInteresct()
public void OverlappingRangeExcludesIntersect()
{
_source.Add(_source1.Connect());
_source.Add(_source2.Connect());
Expand Down
78 changes: 51 additions & 27 deletions src/DynamicData/List/Internal/DynamicCombiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,29 +45,30 @@ public IObservable<IChangeSet<T>> Run()
.Synchronize(_locker)
.Subscribe(changes =>
{
//Populate result list and chck for changes
//Populate result list and check for changes
var notifications = UpdateResultList(sourceLists.Items.AsArray(), resultList, changes);
if (notifications.Count != 0)
{
observer.OnNext(notifications);
}
});

//when an list is removed, need to
//When a list is removed, update all items that were in that list
var removedItem = sourceLists.Connect()
.OnItemRemoved(mc =>
{
//Remove items if required
var notifications = ProcessChanges(sourceLists.Items.AsArray(), resultList, mc.Tracker.Items);
var notifications = UpdateItemSetMemberships(sourceLists.Items.AsArray(), resultList, mc.Tracker.Items);
if (notifications.Count != 0)
{
observer.OnNext(notifications);
}

//On some operators, items not in the removed list can also be affected.
if (_type == CombineOperator.And || _type == CombineOperator.Except)
{
var itemsToCheck = sourceLists.Items.SelectMany(mc2 => mc2.Tracker.Items).ToArray();
var notification2 = ProcessChanges(sourceLists.Items.AsArray(), resultList, itemsToCheck);
var notification2 = UpdateItemSetMemberships(sourceLists.Items.AsArray(), resultList, itemsToCheck);
if (notification2.Count != 0)
{
observer.OnNext(notification2);
Expand All @@ -76,20 +77,21 @@ public IObservable<IChangeSet<T>> Run()
})
.Subscribe();

//when an list is added or removed, need to
//When a list is added, update all items that are in that list
var sourceChanged = sourceLists.Connect()
.WhereReasonsAre(ListChangeReason.Add, ListChangeReason.AddRange)
.ForEachItemChange(mc =>
{
var notifications = ProcessChanges(sourceLists.Items.AsArray(), resultList, mc.Current.Tracker.Items);
var notifications = UpdateItemSetMemberships(sourceLists.Items.AsArray(), resultList, mc.Current.Tracker.Items);
if (notifications.Count != 0)
{
observer.OnNext(notifications);
}

//On some operators, items not in the new list can also be affected.
if (_type == CombineOperator.And || _type == CombineOperator.Except)
{
var notification2 = ProcessChanges(sourceLists.Items.AsArray(), resultList, resultList.ToArray());
var notification2 = UpdateItemSetMemberships(sourceLists.Items.AsArray(), resultList, resultList.ToArray());
if (notification2.Count != 0)
{
observer.OnNext(notification2);
Expand All @@ -102,39 +104,61 @@ public IObservable<IChangeSet<T>> Run()
});
}

private IChangeSet<T> UpdateResultList(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts<T> resultingList, IChangeSet<T> changes)
private IChangeSet<T> UpdateResultList(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts<T> resultList, IChangeSet<T> changes)
{
//child caches have been updated before we reached this point.
changes.Flatten().ForEach(change => { ProcessItem(sourceLists, resultingList, change.Current); });
return resultingList.CaptureChanges();
foreach(var change in changes.Flatten())
{
switch (change.Reason)
{
case ListChangeReason.Add:
case ListChangeReason.Remove:
UpdateItemMembership(change.Current, sourceLists, resultList);
break;

case ListChangeReason.Replace:
UpdateItemMembership(change.Previous.Value, sourceLists, resultList);
UpdateItemMembership(change.Current, sourceLists, resultList);
break;

// Pass through refresh changes:
case ListChangeReason.Refresh:
resultList.Refresh(change.Current);
break;

// A move does not affect contents and so can be ignored:
case ListChangeReason.Moved:
break;

// These should not occur as they are replaced by the Flatten operator:
//case ListChangeReason.AddRange:
//case ListChangeReason.RemoveRange:
//case ListChangeReason.Clear:

default:
throw new ArgumentOutOfRangeException(nameof(change.Reason), "Unsupported change type");
}
}
return resultList.CaptureChanges();
}

private IChangeSet<T> ProcessChanges(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts<T> resultingList, IEnumerable<T> items)
private IChangeSet<T> UpdateItemSetMemberships(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts<T> resultingList, IEnumerable<T> items)
{
//check whether the item should be removed from the list
items.ForEach(item => { ProcessItem(sourceLists, resultingList, item); });
items.ForEach(item => UpdateItemMembership(item, sourceLists, resultingList));
return resultingList.CaptureChanges();
}

private void ProcessItem(MergeContainer[] sourceLists, ChangeAwareListWithRefCounts<T> resultingList, T item)
private void UpdateItemMembership(T item, MergeContainer[] sourceLists, ChangeAwareListWithRefCounts<T> resultList)
{
//check whether the item should be removed from the list
var isInResult = resultingList.Contains(item);
var isInResult = resultList.Contains(item);
var shouldBeInResult = MatchesConstraint(sourceLists, item);

if (shouldBeInResult)
if (shouldBeInResult && !isInResult)
{
if (!isInResult)
{
resultingList.Add(item);
}
resultList.Add(item);
}
else
else if (!shouldBeInResult && isInResult)
{
if (isInResult)
{
resultingList.Remove(item);
}
resultList.Remove(item);
}
}

Expand Down

0 comments on commit 8db149a

Please sign in to comment.