Skip to content

Commit 0db5167

Browse files
AmrAlSayed0amrChrisPulman
authored
Fix stop oaph omitting inappropriately (#3693)
Fixes #3682 <!-- Please be sure to read the [Contribute](https://github.com/reactiveui/reactiveui#contribute) section of the README --> **What kind of change does this PR introduce?** <!-- Bug fix, feature, docs update, ... --> **What is the current behavior?** <!-- You can also link to an open issue here. --> **What is the new behavior?** <!-- If this is a feature change --> For deferred subscriptions in OAPH 2 changes where made: - On first `Value` access INPC is no longer emitted while the `Value` is still the initial value. - After accessing `Value` for the first time, INPC is no longer emitted if the value produced from source is the same as the initial value **What might this PR break?** I don't think anything will break. It doesn't make sense for any UI framework to subscribe to INPC without reading the initial value anyway. **Please check if the PR fulfills these requirements** - [x] Tests for the changes have been added (for bug fixes / features) - [x] Docs have been added / updated (for bug fixes / features) **Other information**: --------- Co-authored-by: amr <[email protected]> Co-authored-by: Chris Pulman <[email protected]>
1 parent 65377aa commit 0db5167

File tree

2 files changed

+67
-10
lines changed

2 files changed

+67
-10
lines changed

src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs

+56-4
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,62 @@ int GetInitialValue()
290290
Assert.Equal(42, result);
291291
}
292292

293-
/// <summary>
294-
/// Tests that Observable As Property Helpers initial value should emit initial value.
295-
/// </summary>
296-
/// <param name="initialValue">The initial value.</param>
293+
/// <summary>Test that Observable As Property Helpers defers subscription with initial function value doesn't call on changed when subscribed.</summary>
294+
/// <param name = "initialValue" >The initial value.</param>
295+
[Theory]
296+
[InlineData(default(int))]
297+
[InlineData(42)]
298+
public void OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSubscribed(int initialValue)
299+
{
300+
var observable = Observable.Empty<int>();
301+
302+
var wasOnChangingCalled = false;
303+
Action<int> onChanging = v => wasOnChangingCalled = true;
304+
var wasOnChangedCalled = false;
305+
Action<int> onChanged = v => wasOnChangedCalled = true;
306+
307+
var fixture = new ObservableAsPropertyHelper<int>(observable, onChanged, onChanging, () => initialValue, true);
308+
309+
Assert.False(fixture.IsSubscribed);
310+
Assert.False(wasOnChangingCalled);
311+
Assert.False(wasOnChangedCalled);
312+
313+
var result = fixture.Value;
314+
315+
Assert.True(fixture.IsSubscribed);
316+
Assert.False(wasOnChangingCalled);
317+
Assert.False(wasOnChangedCalled);
318+
Assert.Equal(initialValue, result);
319+
}
320+
321+
/// <summary>Test that Observable As Property Helpers defers subscription with initial function value doesn't call on changed when source provides initial value after subscription.</summary>
322+
/// <param name = "initialValue" >The initial value.</param>
323+
[Theory]
324+
[InlineData(default(int))]
325+
[InlineData(42)]
326+
public void OAPHDeferSubscriptionWithInitialFuncValueNotCallOnChangedWhenSourceProvidesInitialValue(int initialValue)
327+
{
328+
var observable = new Subject<int>();
329+
330+
var wasOnChangingCalled = false;
331+
Action<int> onChanging = v => wasOnChangingCalled = true;
332+
var wasOnChangedCalled = false;
333+
Action<int> onChanged = v => wasOnChangedCalled = true;
334+
335+
var fixture = new ObservableAsPropertyHelper<int>(observable, onChanged, onChanging, () => initialValue, true);
336+
337+
var result = fixture.Value;
338+
339+
Assert.Equal(initialValue, result);
340+
341+
observable.OnNext(initialValue);
342+
343+
Assert.False(wasOnChangingCalled);
344+
Assert.False(wasOnChangedCalled);
345+
}
346+
347+
/// <summary>Tests that Observable As Property Helpers initial value should emit initial value.</summary>
348+
/// <param name = "initialValue" >The initial value.</param>
297349
[Theory]
298350
[InlineData(default(int))]
299351
[InlineData(42)]

src/ReactiveUI/ObservableForProperty/ObservableAsPropertyHelper.cs

+11-6
Original file line numberDiff line numberDiff line change
@@ -154,18 +154,23 @@ public ObservableAsPropertyHelper(
154154
ex => _thrownExceptions.Value.OnNext(ex))
155155
.DisposeWith(_disposable);
156156

157-
_getInitialValue = getInitialValue!;
157+
_getInitialValue = getInitialValue ??= () => default(T?);
158158

159159
if (deferSubscription)
160160
{
161-
_lastValue = default;
162-
Source = observable.DistinctUntilChanged();
161+
// Although there are no subscribers yet, we should skip all the values that are equal getInitialValue() instead of equal default(T?) because
162+
// default(T?) is never accessible anyway when subscriptions are deferred. We're going to assume that the current value is getInitialValue() even
163+
// if it hasn't been evaluated yet
164+
Source = observable.SkipWhile(x => EqualityComparer<T?>.Default.Equals(x, getInitialValue() /* Don't use field to avoid capturing this */))
165+
.DistinctUntilChanged();
163166
}
164167
else
165168
{
166169
_lastValue = _getInitialValue();
167-
Source = observable.StartWith(_lastValue).DistinctUntilChanged();
168-
Source.Subscribe(_subject).DisposeWith(_disposable);
170+
Source = observable.StartWith(_lastValue)
171+
.DistinctUntilChanged();
172+
Source.Subscribe(_subject)
173+
.DisposeWith(_disposable);
169174
_activated = 1;
170175
}
171176
}
@@ -184,7 +189,7 @@ public T Value
184189
if (localReferenceInCaseDisposeIsCalled is not null)
185190
{
186191
_lastValue = _getInitialValue();
187-
Source.StartWith(_lastValue).Subscribe(_subject).DisposeWith(localReferenceInCaseDisposeIsCalled);
192+
Source.Subscribe(_subject).DisposeWith(localReferenceInCaseDisposeIsCalled);
188193
}
189194
}
190195

0 commit comments

Comments
 (0)