Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
617 changes: 617 additions & 0 deletions src/DynamicData.Tests/Cache/AutoRefreshFixture.Base.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reactive.Concurrency;
using System.Reactive.Linq;

using FluentAssertions;
using Xunit;

using DynamicData.Tests.Utilities;

namespace DynamicData.Tests.Cache;

public static partial class AutoRefreshFixture
{
public class WithPropertyAccessor
: Base
{
[Fact(Skip = "Existing defect: propertyAccessor is not null checked, throws NRE on first notification, instead")]
public void PropertyAccessorIsNull_ThrowsException()
=> FluentActions.Invoking(() => ObservableCacheEx.AutoRefresh(
source: Observable.Never<IChangeSet<Item, int>>(),
propertyAccessor: (null as Expression<Func<Item, int>>)!))
.Should()
.Throw<ArgumentNullException>();

[Fact]
public void PropertyChangedNotificationDoesNotMatchPropertyAccessor_IgnoresNotification()
{
// Setup
using var source = new TestSourceCache<Item, int>(Item.SelectId);

var item1 = new Item() { Id = 1 };
var item2 = new Item() { Id = 2 };
var item3 = new Item() { Id = 3 };

source.AddOrUpdate(new[] { item1, item2, item3 });


// UUT Initialization
using var subscription = BuildUut(source.Connect())
.ValidateSynchronization()
.ValidateChangeSets(Item.SelectId)
.RecordCacheItems(out var results);

results.Error.Should().BeNull();
results.RecordedChangeSets.Count.Should().Be(1, "the initial changeset should propagate");
results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source");
results.HasCompleted.Should().BeFalse("the source has not completed");


// UUT Action
++item2.OtherValue;

results.Error.Should().BeNull();
results.RecordedChangeSets.Skip(1).Should().BeEmpty("the property change notification should have been ignored");
results.HasCompleted.Should().BeFalse("the source has not completed");
}

protected override IObservable<IChangeSet<Item, int>> BuildUut(
IObservable<IChangeSet<Item, int>> source,
TimeSpan? changeSetBuffer = null,
TimeSpan? propertyChangeThrottle = null,
IScheduler? scheduler = null)
=> source.AutoRefresh(
propertyAccessor: static item => item.Value,
changeSetBuffer: changeSetBuffer,
propertyChangeThrottle: propertyChangeThrottle,
scheduler: scheduler);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Reactive.Concurrency;

using FluentAssertions;
using Xunit;

using DynamicData.Tests.Utilities;

namespace DynamicData.Tests.Cache;

public static partial class AutoRefreshFixture
{
public class WithoutPropertyAccessor
: Base
{
[Fact]
public void PropertyChangedNotificationDoesNotSpecifyPropertyName_ItemRefreshes()
{
// Setup
using var source = new TestSourceCache<Item, int>(Item.SelectId);

var item1 = new Item() { Id = 1 };
var item2 = new Item() { Id = 2 };
var item3 = new Item() { Id = 3 };

source.AddOrUpdate(new[] { item1, item2, item3 });


// UUT Initialization
using var subscription = BuildUut(source.Connect())
.ValidateSynchronization()
.ValidateChangeSets(Item.SelectId)
.RecordCacheItems(out var results);

results.Error.Should().BeNull();
results.RecordedChangeSets.Count.Should().Be(1, "the initial changeset should propagate");
results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "3 items were added to the source");
results.HasCompleted.Should().BeFalse("the source has not completed");


// UUT Action
item2.RaiseAllPropertiesChanged();

results.Error.Should().BeNull();
results.RecordedChangeSets.Skip(1).Count().Should().Be(1, "1 item published a property change notification");
results.RecordedChangeSets.Skip(1).First().Count.Should().Be(1, "1 item published a property change notification");
results.RecordedChangeSets.Skip(1).First().Refreshes.Should().Be(1, "1 item published a property change notification");
results.RecordedChangeSets.Skip(1).First().First().Current.Should().Be(item2, "item #2 published a property change notification");
results.RecordedItemsByKey.Values.Should().BeEquivalentTo(source.Items, "no source operations were performed");
results.HasCompleted.Should().BeFalse("the source has not completed");
}

protected override IObservable<IChangeSet<Item, int>> BuildUut(
IObservable<IChangeSet<Item, int>> source,
TimeSpan? changeSetBuffer = null,
TimeSpan? propertyChangeThrottle = null,
IScheduler? scheduler = null)
=> source.AutoRefresh(
changeSetBuffer: changeSetBuffer,
propertyChangeThrottle: propertyChangeThrottle,
scheduler: scheduler);
}
}
157 changes: 48 additions & 109 deletions src/DynamicData.Tests/Cache/AutoRefreshFixture.cs
Original file line number Diff line number Diff line change
@@ -1,127 +1,66 @@
using System;
using System.Linq;
using System.Reactive.Linq;

using DynamicData.Binding;
using DynamicData.Tests.Domain;

using FluentAssertions;

using Xunit;
using System.ComponentModel;

namespace DynamicData.Tests.Cache;

public class AutoRefreshFixture
public static partial class AutoRefreshFixture
{
[Fact]
public void AutoRefresh()
public enum NotificationStrategy
{
var items = Enumerable.Range(1, 100).Select(i => new Person("Person" + i, 1)).ToArray();

//result should only be true when all items are set to true
using var cache = new SourceCache<Person, string>(m => m.Name);
using var results = cache.Connect().AutoRefresh(p => p.Age).AsAggregator();
cache.AddOrUpdate(items);

results.Data.Count.Should().Be(100);
results.Messages.Count.Should().Be(1);

items[0].Age = 10;
results.Data.Count.Should().Be(100);
results.Messages.Count.Should().Be(2);

results.Messages[1].First().Reason.Should().Be(ChangeReason.Refresh);

//remove an item and check no change is fired
var toRemove = items[1];
cache.Remove(toRemove);
results.Data.Count.Should().Be(99);
results.Messages.Count.Should().Be(3);
toRemove.Age = 100;
results.Messages.Count.Should().Be(3);

//add it back in and check it updates
cache.AddOrUpdate(toRemove);
results.Messages.Count.Should().Be(4);
toRemove.Age = 101;
results.Messages.Count.Should().Be(5);

results.Messages.Last().First().Reason.Should().Be(ChangeReason.Refresh);
}

[Fact]
public void AutoRefreshFromObservable()
{
var items = Enumerable.Range(1, 100).Select(i => new Person("Person" + i, 1)).ToArray();

//result should only be true when all items are set to true
using var cache = new SourceCache<Person, string>(m => m.Name);
using var results = cache.Connect().AutoRefreshOnObservable(p => p.WhenAnyPropertyChanged()).AsAggregator();
cache.AddOrUpdate(items);

results.Data.Count.Should().Be(100);
results.Messages.Count.Should().Be(1);

items[0].Age = 10;
results.Data.Count.Should().Be(100);
results.Messages.Count.Should().Be(2);

results.Messages[1].First().Reason.Should().Be(ChangeReason.Refresh);

//remove an item and check no change is fired
var toRemove = items[1];
cache.Remove(toRemove);
results.Data.Count.Should().Be(99);
results.Messages.Count.Should().Be(3);
toRemove.Age = 100;
results.Messages.Count.Should().Be(3);

//add it back in and check it updates
cache.AddOrUpdate(toRemove);
results.Messages.Count.Should().Be(4);
toRemove.Age = 101;
results.Messages.Count.Should().Be(5);

results.Messages.Last().First().Reason.Should().Be(ChangeReason.Refresh);
Immediate,
Asynchronous
}

[Fact]
public void MakeSelectMagicWorkWithObservable()
public class Item
: INotifyPropertyChanged
{
var initialItem = new IntHolder(1, "Initial Description");

var sourceList = new SourceList<IntHolder>();
sourceList.Add(initialItem);

var descriptionStream = sourceList.Connect().AutoRefresh(intHolder => intHolder!.Description).Transform(intHolder => intHolder!.Description, true).Do(x => { }) // <--- Add break point here to check the overload fixes it
.Bind(out var resultCollection);

using (descriptionStream.Subscribe())
public static int SelectId(Item item)
=> item.Id;

public required int Id
{
var newDescription = "New Description";
initialItem.Description = newDescription;

newDescription.Should().Be(resultCollection[0]);
//Assert.AreEqual(newDescription, resultCollection[0]);
get => _id;
init => _id = value;
}
}

public class IntHolder(int value, string description) : AbstractNotifyPropertyChanged
{
public string _description_ = description;

public int _value = value;

public string Description

public bool HasSubscriptions
=> PropertyChanged is not null;

public int OtherValue
{
get => _description_;
set => SetAndRaise(ref _description_, value);
get => _otherValue;
set
{
if (_otherValue == value)
return;

_otherValue = value;

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OtherValue)));
}
}

public int Value
{
get => _value;
set => SetAndRaise(ref _value, value);
set
{
if (_value == value)
return;

_value = value;

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}

public event PropertyChangedEventHandler? PropertyChanged;

public void RaiseAllPropertiesChanged()
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));

private readonly int _id;

private int _otherValue;
private int _value;
}
}
Loading
Loading