diff --git a/.editorconfig b/.editorconfig index 033ce490..e3b598cc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -345,7 +345,7 @@ dotnet_diagnostic.SA1110.severity = error dotnet_diagnostic.SA1111.severity = error dotnet_diagnostic.SA1112.severity = error dotnet_diagnostic.SA1113.severity = error -dotnet_diagnostic.SA1114.severity = error +dotnet_diagnostic.SA1114.severity = none dotnet_diagnostic.SA1115.severity = error dotnet_diagnostic.SA1116.severity = none dotnet_diagnostic.SA1117.severity = error diff --git a/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs b/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs index 04fc77ca..ad5d9086 100644 --- a/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs +++ b/src/DynamicData.Tests/Cache/TrueForAnyFixture.cs @@ -2,6 +2,8 @@ using System.Reactive.Linq; using System.Reactive.Subjects; +using DynamicData.Tests.Utilities; + using FluentAssertions; using Xunit; @@ -77,6 +79,26 @@ public void MultipleValuesReturnTrue() subscribed.Dispose(); } + // https://github.com/reactivemarbles/DynamicData/issues/922 + [Fact] + public void ValuesPublishedOnSubscriptionDoNotTriggerPrematureOutput() + { + var item1 = new ObjectWithObservable(1); + var item2 = new ObjectWithObservable(2); + + item2.InvokeObservable(true); + + _source.AddOrUpdate(item1); + _source.AddOrUpdate(item2); + + using var subscription = _observable + .ValidateSynchronization() + .RecordValues(out var results); + + results.RecordedValues.Count.Should().Be(1, because: "No items were added to the source, and no value changes were made to the items"); + results.RecordedValues[0].Should().Be(true, because: "One of the two items in the source has a true value"); + } + private class ObjectWithObservable(int id) { private readonly ISubject _changed = new Subject(); diff --git a/src/DynamicData/Cache/Internal/TrueFor.cs b/src/DynamicData/Cache/Internal/TrueFor.cs index 333cc69d..eba0479a 100644 --- a/src/DynamicData/Cache/Internal/TrueFor.cs +++ b/src/DynamicData/Cache/Internal/TrueFor.cs @@ -18,16 +18,26 @@ internal sealed class TrueFor(IObservable> _source = source ?? throw new ArgumentNullException(nameof(source)); - public IObservable Run() => Observable.Create( - observer => - { - var transformed = _source.Transform(t => new ObservableWithValue(t, _observableSelector(t))).Publish(); - var inlineChanges = transformed.MergeMany(t => t.Observable); - var queried = transformed.ToCollection(); - - // nb: we do not care about the inline change because we are only monitoring it to cause a re-evaluation of all items - var publisher = queried.CombineLatest(inlineChanges, (items, _) => _collectionMatcher(items)).DistinctUntilChanged().SubscribeSafe(observer); - - return new CompositeDisposable(publisher, transformed.Connect()); - }); + public IObservable Run() + => Observable.Create(observer => + { + var itemsWithValues = _source + .Transform(item => new ObservableWithValue( + item: item, + source: _observableSelector.Invoke(item))) + .Publish(); + + var subscription = Observable.CombineLatest( + // Make sure we subscribe to ALL of the items before we make the first evaluation of the collection, so any values published on-subscription don't trigger a re-evaluation of the matcher method. + first: itemsWithValues.MergeMany(item => item.Observable), + second: itemsWithValues.ToCollection(), + // We don't need to actually look at the changed values, we just need them as a trigger to re-evaluate the matcher method. + resultSelector: (_, itemsWithValues) => _collectionMatcher.Invoke(itemsWithValues)) + .DistinctUntilChanged() + .SubscribeSafe(observer); + + return new CompositeDisposable( + subscription, + itemsWithValues.Connect()); + }); }