Skip to content

Commit

Permalink
Updated property selectors to handle nullable types
Browse files Browse the repository at this point in the history
The code changes involve updating the property selectors in the Observable class to handle nullable types. This includes modifying the function signatures and return types of ObservePropertyChanged and ObservePropertyChanging methods. Additionally, new test cases have been added to validate these changes, ensuring that properties can be observed correctly even when they are null or their values change.
  • Loading branch information
michaelstonis committed Mar 5, 2024
1 parent 646e94f commit 423b2f5
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 10 deletions.
18 changes: 9 additions & 9 deletions src/R3/Factories/ObserveProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public static Observable<TProperty> ObservePropertyChanged<T, TProperty>(this T
/// `propertySelector1` and `propertySelector2` must be a Func specifying a simple property. For example, it extracts "Foo" from `x => x.Foo`.
/// </summary>
public static Observable<TProperty2> ObservePropertyChanged<T, TProperty1, TProperty2>(this T value,
Func<T, TProperty1> propertySelector1,
Func<T, TProperty1?> propertySelector1,
Func<TProperty1, TProperty2> propertySelector2,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
Expand All @@ -42,7 +42,7 @@ public static Observable<TProperty2> ObservePropertyChanged<T, TProperty1, TProp
var property1Name = propertySelector1Expr!.Substring(propertySelector1Expr.LastIndexOf('.') + 1);
var property2Name = propertySelector2Expr!.Substring(propertySelector2Expr.LastIndexOf('.') + 1);

return new ObservePropertyChanged<T, TProperty1>(value, propertySelector1, property1Name, true, cancellationToken)
return new ObservePropertyChanged<T, TProperty1?>(value, propertySelector1, property1Name, true, cancellationToken)
.Select(
(propertySelector2, property2Name, pushCurrentValueOnSubscribe, cancellationToken),
(firstPropertyValue, state) =>
Expand All @@ -59,8 +59,8 @@ firstPropertyValue is not null
/// `propertySelector1`, `propertySelector2`, and `propertySelector3` must be a Func specifying a simple property. For example, it extracts "Foo" from `x => x.Foo`.
/// </summary>
public static Observable<TProperty3> ObservePropertyChanged<T, TProperty1, TProperty2, TProperty3>(this T value,
Func<T, TProperty1> propertySelector1,
Func<TProperty1, TProperty2> propertySelector2,
Func<T, TProperty1?> propertySelector1,
Func<TProperty1, TProperty2?> propertySelector2,
Func<TProperty2, TProperty3> propertySelector3,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
Expand All @@ -79,12 +79,12 @@ public static Observable<TProperty3> ObservePropertyChanged<T, TProperty1, TProp
var property2Name = propertySelector2Expr!.Substring(propertySelector2Expr.LastIndexOf('.') + 1);
var property3Name = propertySelector3Expr!.Substring(propertySelector3Expr.LastIndexOf('.') + 1);

return new ObservePropertyChanged<T, TProperty1>(value, propertySelector1, property1Name, true, cancellationToken)
return new ObservePropertyChanged<T, TProperty1?>(value, propertySelector1, property1Name, true, cancellationToken)
.Select(
(propertySelector2, property2Name, propertySelector3, property3Name, pushCurrentValueOnSubscribe, cancellationToken),
(firstPropertyValue, state) =>
firstPropertyValue is not null
? new ObservePropertyChanged<TProperty1, TProperty2>(firstPropertyValue, state.propertySelector2, state.property2Name, true, state.cancellationToken)
? new ObservePropertyChanged<TProperty1, TProperty2?>(firstPropertyValue, state.propertySelector2, state.property2Name, true, state.cancellationToken)
.Select(
(state.propertySelector3, state.property3Name, pushCurrentValueOnSubscribe, cancellationToken),
(secondPropertyValue, state2) =>
Expand Down Expand Up @@ -121,7 +121,7 @@ public static Observable<TProperty> ObservePropertyChanging<T, TProperty>(this T
/// `propertySelector1` and `propertySelector2` must be a Func specifying a simple property. For example, it extracts "Foo" from `x => x.Foo`.
/// </summary>
public static Observable<TProperty2> ObservePropertyChanging<T, TProperty1, TProperty2>(this T value,
Func<T, TProperty1> propertySelector1,
Func<T, TProperty1?> propertySelector1,
Func<TProperty1, TProperty2> propertySelector2,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
Expand Down Expand Up @@ -151,8 +151,8 @@ firstPropertyValue is not null
/// `propertySelector1`, `propertySelector2`, and `propertySelector3` must be a Func specifying a simple property. For example, it extracts "Foo" from `x => x.Foo`.
/// </summary>
public static Observable<TProperty3> ObservePropertyChanging<T, TProperty1, TProperty2, TProperty3>(this T value,
Func<T, TProperty1> propertySelector1,
Func<TProperty1, TProperty2> propertySelector2,
Func<T, TProperty1?> propertySelector1,
Func<TProperty1, TProperty2?> propertySelector2,
Func<TProperty2, TProperty3> propertySelector3,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
Expand Down
257 changes: 256 additions & 1 deletion tests/R3.Tests/FactoryTests/ObservePropertyTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace R3.Tests.FactoryTests;
Expand All @@ -21,6 +21,24 @@ public void PropertyChanged()
liveList.AssertEqual([0, 1]);
}

[Fact]
public void NullablePropertyChanged()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanged(x => x.NullableInnerPropertyChanged)
.ToLiveList();

liveList.AssertEqual([null]);

var nipc = new ChangesProperty();

propertyChanger.NullableInnerPropertyChanged = nipc;

liveList.AssertEqual([null, nipc]);
}

[Fact]
public void NestedPropertyChanged()
{
Expand All @@ -45,6 +63,52 @@ public void NestedPropertyChanged()
liveList.AssertEqual([0, 1, 2]);
}

[Fact]
public void NullableNestedPropertyChanged()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanged(x => x.NullableInnerPropertyChanged, x => x.Value)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([0]);

propertyChanger.NullableInnerPropertyChanged.Value = 1;

liveList.AssertEqual([0, 1]);

propertyChanger.NullableInnerPropertyChanged.Value = 2;

liveList.AssertEqual([0, 1, 2]);
}

[Fact]
public void NullableNestedPropertyChangedWithNullableEndProperty()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanged(x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([null]);

var nipc = new ChangesProperty();

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc;

liveList.AssertEqual([null, nipc]);
}

[Fact]
public void DoubleNestedPropertyChanged()
{
Expand All @@ -69,6 +133,55 @@ public void DoubleNestedPropertyChanged()
liveList.AssertEqual([0, 1]);
}

[Fact]
public void NullableDoubleNestedPropertyChanged()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanged(x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged, x => x.Value)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = new();

liveList.AssertEqual([0]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged.Value = 1;

liveList.AssertEqual([0, 1]);
}

[Fact]
public void NullableDoubleNestedPropertyChangedWithNullableEndProperty()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanged(x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = new();

liveList.AssertEqual([null]);

var nipc = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc;

liveList.AssertEqual([null, nipc]);
}

[Fact]
public void PropertyChanging()
{
Expand All @@ -85,6 +198,28 @@ public void PropertyChanging()
liveList.AssertEqual([0, 0]);
}

[Fact]
public void NullablePropertyChanging()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanging(x => x.NullableInnerPropertyChanged)
.ToLiveList();

liveList.AssertEqual([null]);

var nipc1 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged = nipc1;

liveList.AssertEqual([null, null]);

var nipc2 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged = nipc2;

liveList.AssertEqual([null, null, nipc1]);
}

[Fact]
public void NestedPropertyChanging()
{
Expand All @@ -109,6 +244,61 @@ public void NestedPropertyChanging()
liveList.AssertEqual([0, 0, 1]);
}

[Fact]
public void NullableNestedPropertyChanging()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanging(x => x.NullableInnerPropertyChanged, x => x.Value)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([0]);

propertyChanger.NullableInnerPropertyChanged.Value = 1;

liveList.AssertEqual([0, 0]);

propertyChanger.NullableInnerPropertyChanged.Value = 2;

liveList.AssertEqual([0, 0, 1]);
}

[Fact]
public void NullableNestedPropertyChangingWithNullableEndProperty()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanging(x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([null]);

var nipc1 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc1;

liveList.AssertEqual([null, null]);

var nipc2 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc2;

liveList.AssertEqual([null, null, nipc1]);

var nipc3 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc3;

liveList.AssertEqual([null, null, nipc1, nipc2]);
}

[Fact]
public void DoubleNestedPropertyChanging()
{
Expand Down Expand Up @@ -137,10 +327,69 @@ public void DoubleNestedPropertyChanging()
liveList.AssertEqual([0, 0, 1]);
}

[Fact]
public void NullableDoubleNestedPropertyChanging()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanging(x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged, x => x.Value)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = new();

liveList.AssertEqual([0]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged.Value = 1;

liveList.AssertEqual([0, 0]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged.Value = 2;

liveList.AssertEqual([0, 0, 1]);
}

[Fact]
public void NullableDoubleNestedPropertyChangingWithNullableEndProperty()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanging(x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged, x => x.NullableInnerPropertyChanged)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged = new();

liveList.AssertEqual([]);

propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged = new();

liveList.AssertEqual([null]);

var nipc1 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc1;

liveList.AssertEqual([null, null]);

var nipc2 = new ChangesProperty();
propertyChanger.NullableInnerPropertyChanged.NullableInnerPropertyChanged.NullableInnerPropertyChanged = nipc2;

liveList.AssertEqual([null, null, nipc1]);
}

class ChangesProperty : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _value;
private ChangesProperty _innerPropertyChanged = default!;
private ChangesProperty? _nullableInnerPropertyChanged;

public event PropertyChangedEventHandler? PropertyChanged;
public event PropertyChangingEventHandler? PropertyChanging;
Expand All @@ -157,6 +406,12 @@ public ChangesProperty InnerPropertyChanged
set => SetField(ref _innerPropertyChanged, value);
}

public ChangesProperty? NullableInnerPropertyChanged
{
get => _nullableInnerPropertyChanged;
set => SetField(ref _nullableInnerPropertyChanged, value);
}

private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Expand Down

0 comments on commit 423b2f5

Please sign in to comment.