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

New SortAndPage operator #917

Merged
merged 3 commits into from
Jul 7, 2024
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

Large diffs are not rendered by default.

390 changes: 390 additions & 0 deletions src/DynamicData.Tests/Cache/SortAndPageAndBindFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using DynamicData.Binding;
using DynamicData.Tests.Domain;
using FluentAssertions;
using Xunit;

namespace DynamicData.Tests.Cache;


public sealed class SortAndPageAndBindWithImplicitOptionsFixtureReadOnlyCollection : SortAndPageAndBindFixtureBase
{
protected override (ChangeSetAggregator<Person, string> aggregator, IList<Person> list) SetUpTests()
{

var aggregator = Source.Connect()
.SortAndPage(Comparer, PageRequests)
// no sort and bind options. These are extracted from the SortAndPage context
.Bind(out var list)
.AsAggregator();

return (aggregator, list);
}
}

public sealed class SortAndPageAndBindFixtureReadOnlyCollection : SortAndPageAndBindFixtureBase
{
protected override (ChangeSetAggregator<Person, string> aggregator, IList<Person> list) SetUpTests()
{

var aggregator = Source.Connect()
.SortAndPage(Comparer, PageRequests)
.Bind(out var list, new SortAndBindOptions())
.AsAggregator();

return (aggregator, list);
}
}

public sealed class SortAndPageAndBindWithImplicitOptionsFixture : SortAndPageAndBindFixtureBase
{
protected override (ChangeSetAggregator<Person, string> aggregator, IList<Person> list) SetUpTests()
{
var list = new List<Person>();

var aggregator = Source.Connect()
.SortAndPage(Comparer, PageRequests)
// no sort and bind options. These are extracted from the SortAndPage context
.Bind(list)
.AsAggregator();

return (aggregator, list);
}
}

public sealed class SortAndPageAndBindFixture : SortAndPageAndBindFixtureBase
{
protected override (ChangeSetAggregator<Person, string> aggregator, IList<Person> list) SetUpTests()
{
var list = new List<Person>();

var aggregator = Source.Connect()
.SortAndPage(Comparer, PageRequests)
.SortAndBind(list, new SortAndBindOptions())
.AsAggregator();

return (aggregator, list);
}
}

public abstract class SortAndPageAndBindFixtureBase : IDisposable
{

protected readonly SourceCache<Person, string> Source = new(p => p.Name);
protected readonly IComparer<Person> Comparer = SortExpressionComparer<Person>.Ascending(p => p.Age).ThenByAscending(p => p.Name);
protected readonly ISubject<IPageRequest> PageRequests = new BehaviorSubject<IPageRequest>(new PageRequest(0, 25));

protected readonly ChangeSetAggregator<Person, string> Aggregator;
protected readonly IList<Person> List;

protected SortAndPageAndBindFixtureBase()
{
// It's ok in this case to call VirtualMemberCallInConstructor

#pragma warning disable CA2214
// ReSharper disable once VirtualMemberCallInConstructor
var args = SetUpTests();
#pragma warning restore CA2214

Aggregator = args.aggregator;
List = args.list;
}


protected abstract (ChangeSetAggregator<Person, string> aggregator, IList<Person> list) SetUpTests();


[Fact]
public void PageGreaterThanNumberOfPagesAvailable()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid());
Source.AddOrUpdate(people);

// should select the last page
PageRequests.OnNext(new PageRequest(10, 25));

var expectedResult = people.OrderBy(p => p, Comparer).Skip(75).Take(25).ToList();

List.Should().BeEquivalentTo(expectedResult);
}


[Fact]
public void OverlappingShift()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid());
Source.AddOrUpdate(people);

PageRequests.OnNext(new PageRequest(3, 10));

// for first batch, it should use the results of the _PageRequests subject (if a behaviour subject is used).
var expectedResult = people.OrderBy(p => p, Comparer).Skip(20).Take(10).ToList();
List.Should().BeEquivalentTo(expectedResult);
}

[Fact]
public void AddFirstInRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// insert right at beginning
var person = new Person("_FirstPerson", 1);
Source.AddOrUpdate(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(2);

var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Remove);
firstChange.Current.Should().Be(new Person("P025", 25));

var secondChange = changes.Skip(1).First();
secondChange.Reason.Should().Be(ChangeReason.Add);
secondChange.Current.Should().Be(person);

// check for correctness of resulting collection
people.Add(person);

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();
List.SequenceEqual(expectedResult).Should().Be(true);
}


[Fact]
public void AddOutsideOfRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// insert right at end
var person = new Person("X_Last", 100);
Source.AddOrUpdate(person);

// only the initials message should have been received
Aggregator.Messages.Count.Should().Be(1);


people.Add(person);
var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();
List.SequenceEqual(expectedResult).Should().Be(true);
}

[Fact]
public void UpdateMoveOutOfRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// Change an item so it moves from in range to out of range
var person = new Person("P012", 50);
Source.AddOrUpdate(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(2);


var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Remove);
firstChange.Current.Should().Be(new Person("P012", 50));

var secondChange = changes.Skip(1).First();
secondChange.Reason.Should().Be(ChangeReason.Add);
secondChange.Current.Should().Be(new Person("P026", 26));

// check for correctness of resulting collection
people = people.OrderBy(p => p, Comparer).ToList();
people[11] = person;

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();
List.SequenceEqual(expectedResult).Should().Be(true);
}
[Fact]
public void UpdateStayRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// Update an item, but keep it withing the expected virtual range.
var person = new Person("P012", -1);
Source.AddOrUpdate(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(1);

var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Update);
firstChange.Current.Should().Be(new Person("P012", -1));
firstChange.Previous.Value.Should().Be(new Person("P012", 12));

// check for correctness of resulting collection
people = people.OrderBy(p => p, Comparer).ToList();
people[11] = person;

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();
List.SequenceEqual(expectedResult).Should().Be(true);
}



[Fact]
public void UpdateOutOfRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// insert right at beginning
var person = new Person("P050", 100);
Source.AddOrUpdate(person);

// only the initials message should have been received
Aggregator.Messages.Count.Should().Be(1);

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();

List.SequenceEqual(expectedResult).Should().Be(true);
}


[Fact]
public void RemoveRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// remove an element from the active range
var person = new Person("P012", 12);
Source.Remove(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(2);

var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Remove);
firstChange.Current.Should().Be(person);

var secondChange = changes.Skip(1).First();
secondChange.Reason.Should().Be(ChangeReason.Add);
secondChange.Current.Should().Be(new Person("P026", 26));

// check for correctness of resulting collection
people.Remove(person);

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();

List.SequenceEqual(expectedResult).Should().Be(true);
}

[Fact]
public void RemoveOutOfRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

// insert right at beginning
var person = new Person("P050", 50);
Source.Remove(person);

// only the initials message should have been received
Aggregator.Messages.Count.Should().Be(1);

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();

List.SequenceEqual(expectedResult).Should().Be(true);
}


[Fact]
public void RefreshInRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

var person = people.Single(p => p.Name == "P012");
Source.Refresh(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(1);

var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Refresh);
}

[Fact]
public void RefreshWithInlineChangeInRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

var person = people.Single(p => p.Name == "P012");

// The item will move within the virtual range, so be propagated as a refresh
person.Age = 5;
Source.Refresh(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(1);

var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Refresh);

var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();

List.SequenceEqual(expectedResult).Should().Be(true);
}

[Fact]
public void RefreshWithInlineChangeOutsideRange()
{
var people = Enumerable.Range(1, 100).Select(i => new Person($"P{i:000}", i)).OrderBy(p => Guid.NewGuid()).ToList();
Source.AddOrUpdate(people);

var person = people.Single(p => p.Name == "P012");

// The item will move outside the virtual range, resulting in a remove and index shift
person.Age = 50;
Source.Refresh(person);

Aggregator.Messages.Count.Should().Be(2);

var changes = Aggregator.Messages[1];
changes.Count.Should().Be(2);

var firstChange = changes.First();
firstChange.Reason.Should().Be(ChangeReason.Remove);
firstChange.Current.Should().Be(new Person("P012", 50));

var secondChange = changes.Skip(1).First();
secondChange.Reason.Should().Be(ChangeReason.Add);
secondChange.Current.Should().Be(new Person("P026", 26));


var expectedResult = people.OrderBy(p => p, Comparer).Take(25).ToList();

List.SequenceEqual(expectedResult).Should().Be(true);
}


public void Dispose()
{
Source.Dispose();
Aggregator.Dispose();
PageRequests.OnCompleted();
}
}
Loading
Loading