diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt index 92fa2bb0..e0755b97 100644 --- a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt +++ b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet8_0.verified.txt @@ -1885,6 +1885,9 @@ namespace DynamicData public static System.IObservable> Switch(this System.IObservable>> sources) where TObject : notnull where TKey : notnull { } + public static System.IObservable> Switch(this System.IObservable>>> sources) + where TObject : notnull + where TKey : notnull { } public static System.IObservable> ToCollection(this System.IObservable> source) where TObject : notnull where TKey : notnull { } diff --git a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt index 5e852c6d..fd9de119 100644 --- a/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt +++ b/src/DynamicData.Tests/API/ApiApprovalTests.DynamicDataTests.DotNet9_0.verified.txt @@ -1876,6 +1876,9 @@ namespace DynamicData public static System.IObservable> Switch(this System.IObservable>> sources) where TObject : notnull where TKey : notnull { } + public static System.IObservable> Switch(this System.IObservable>>> sources) + where TObject : notnull + where TKey : notnull { } public static System.IObservable> ToCollection(this System.IObservable> source) where TObject : notnull where TKey : notnull { } diff --git a/src/DynamicData.Tests/Cache/VirtualizedSwitchFixture.cs b/src/DynamicData.Tests/Cache/VirtualizedSwitchFixture.cs new file mode 100644 index 00000000..35518263 --- /dev/null +++ b/src/DynamicData.Tests/Cache/VirtualizedSwitchFixture.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using DynamicData; +using DynamicData.Tests.Utilities; +using FluentAssertions; +using Xunit; + +namespace ExternalConsumerTests.Cache; + +public class VirtualizedSwitchFixture +{ + [Fact] + public void SwitchingAwayFromVirtualizedSourceRemovesItsItems() + { + using var enabled = new BehaviorSubject(true); + using var source = new SourceCache(value => value); + source.AddOrUpdate(Enumerable.Range(0, 20)); + + IObservable>> virtualized = CreateVirtualized(source, 10); + IObservable>>> sources = enabled.Select( + isEnabled => isEnabled ? virtualized : Observable.Empty>>()); + IObservable> switched = sources.Switch(); + using var subscription = switched + .ValidateChangeSets(static value => value) + .RecordCacheItems(out var results); + + results.RecordedChangeSets.Should().ContainSingle(); + results.RecordedChangeSets[0].Adds.Should().Be(10); + results.RecordedItemsByKey.Values.Should().BeEquivalentTo(Enumerable.Range(0, 10)); + + enabled.OnNext(false); + + results.RecordedChangeSets.Should().HaveCount(2); + results.RecordedChangeSets[1].Removes.Should().Be(10); + results.RecordedItemsByKey.Should().BeEmpty(); + } + + [Fact] + public void SwitchingBetweenVirtualizedSourcesReplacesVisibleItems() + { + using var first = new SourceCache(value => value); + using var second = new SourceCache(value => value); + first.AddOrUpdate(Enumerable.Range(0, 10)); + second.AddOrUpdate(Enumerable.Range(100, 10)); + + IObservable>> firstVirtualized = CreateVirtualized(first, 3); + IObservable>> secondVirtualized = CreateVirtualized(second, 3); + using var sources = new BehaviorSubject>>>(firstVirtualized); + IObservable> switched = sources.Switch(); + using var subscription = switched + .ValidateChangeSets(static value => value) + .RecordCacheItems(out var results); + + results.RecordedItemsByKey.Values.Should().BeEquivalentTo([0, 1, 2]); + + sources.OnNext(secondVirtualized); + + results.RecordedChangeSets.Should().HaveCount(3); + results.RecordedChangeSets[1].Removes.Should().Be(3); + results.RecordedChangeSets[2].Adds.Should().Be(3); + results.RecordedItemsByKey.Values.Should().BeEquivalentTo([100, 101, 102]); + } + + private static IObservable>> CreateVirtualized(SourceCache source, int size) => + source.Connect().SortAndVirtualize( + Comparer.Default, + Observable.Return(new VirtualRequest(0, size))); +} diff --git a/src/DynamicData/Cache/ObservableCacheEx.Switch.cs b/src/DynamicData/Cache/ObservableCacheEx.Switch.cs index 9c5d06c9..d8e99726 100644 --- a/src/DynamicData/Cache/ObservableCacheEx.Switch.cs +++ b/src/DynamicData/Cache/ObservableCacheEx.Switch.cs @@ -37,6 +37,15 @@ public static IObservable> Switch(this return sources.Select(cache => cache.Connect()).Switch(); } + /// + /// An observable that emits virtualized changeset streams. + public static IObservable> Switch(this IObservable>>> sources) + where TObject : notnull + where TKey : notnull + { + return ObservableCacheEx.Switch((IObservable>>)sources); + } + /// /// Subscribes to the latest inner changeset stream, unsubscribing from the previous one on each switch. /// When switching, the old source's items are removed and the new source's items are added.