Skip to content

Commit

Permalink
Merge pull request #158 from michaelstonis/feature/nullable-property-…
Browse files Browse the repository at this point in the history
…changed

Updated property selectors to handle nullable types
  • Loading branch information
neuecc authored Mar 6, 2024
2 parents 646e94f + 423b2f5 commit dee1eac
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 dee1eac

Please sign in to comment.