Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [MVUX] Improve nullable support #2080

Merged
merged 7 commits into from
Dec 22, 2023
Merged
6 changes: 3 additions & 3 deletions src/Uno.Extensions.Reactive.Messaging/StateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public static async ValueTask Update<TEntity, TKey>(this IState<TEntity> state,
{
case EntityChange.Updated:
var updatedEntityKey = keySelector(message.Value);
await state.UpdateData(current => current.IsSome(out var entity) && AreKeyEquals(updatedEntityKey, keySelector(entity)) ? message.Value : current, ct);
await state.UpdateDataAsync(current => current.IsSome(out var entity) && AreKeyEquals(updatedEntityKey, keySelector(entity)) ? message.Value : current, ct);
break;

case EntityChange.Deleted:
var removedEntityKey = keySelector(message.Value);
await state.UpdateData(current => current.IsSome(out var entity) && AreKeyEquals(removedEntityKey, keySelector(entity)) ? Option<TEntity>.None() : current, ct);
await state.UpdateDataAsync(current => current.IsSome(out var entity) && AreKeyEquals(removedEntityKey, keySelector(entity)) ? Option<TEntity>.None() : current, ct);
break;
}

Expand Down Expand Up @@ -63,7 +63,7 @@ public static async ValueTask Update<TEntity, TKey>(this IListState<TEntity> lis

case EntityChange.Updated:
var updatedItemKey = keySelector(message.Value);
await listState.UpdateAsync(item => AreKeyEquals(updatedItemKey, keySelector(item)), _ => message.Value, ct);
await listState.UpdateAllAsync(item => AreKeyEquals(updatedItemKey, keySelector(item)), _ => message.Value, ct);
break;
}

Expand Down
12 changes: 6 additions & 6 deletions src/Uno.Extensions.Reactive.Tests/Core/Given_StateImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public async Task When_Empty_Then_CanBeUpdatedByMessage()
{
var (result, sut) = new StateImpl<string>(Context, Option<string>.None()).Record();

await sut.UpdateMessage(msg => msg.Data("42"), CT);
await sut.UpdateMessageAsync(msg => msg.Data("42"), CT);

result.Should().Be(r => r
.Message(Data.None, Progress.Final, Error.No)
Expand All @@ -48,7 +48,7 @@ public async Task When_Empty_Then_CanBeUpdatedByValue()
{
var (result, sut) = new StateImpl<string>(Context, Option<string>.None()).Record();

await sut.UpdateData(_ => "42", CT);
await sut.UpdateDataAsync(_ => "42", CT);

result.Should().Be(r => r
.Message(Data.None, Progress.Final, Error.No)
Expand All @@ -60,7 +60,7 @@ public async Task When_Empty_Then_CanBeUpdated()
{
var (result, sut) = new StateImpl<string>(Context, Option<string>.None()).Record();

await sut.Update(_ => "42", CT);
await sut.UpdateAsync(_ => "42", CT);

result.Should().Be(r => r
.Message(Data.None, Progress.Final, Error.No)
Expand All @@ -72,7 +72,7 @@ public async Task When_Value_Then_CanBeUpdatedByMessage()
{
var (result, sut) = new StateImpl<string>(Context, Option<string>.Some("0")).Record();

await sut.UpdateMessage(msg => msg.Data("42"), CT);
await sut.UpdateMessageAsync(msg => msg.Data("42"), CT);

result.Should().Be(r => r
.Message("0", Progress.Final, Error.No)
Expand All @@ -84,7 +84,7 @@ public async Task When_Value_Then_CanBeUpdatedByValue()
{
var (result, sut) = new StateImpl<string>(Context, Option<string>.Some("0")).Record();

await sut.UpdateData(_ => "42", CT);
await sut.UpdateDataAsync(_ => "42", CT);

result.Should().Be(r => r
.Message("0", Progress.Final, Error.No)
Expand All @@ -96,7 +96,7 @@ public async Task When_Value_Then_CanBeUpdated()
{
var (result, sut) = new StateImpl<string>(Context, Option<string>.Some("0")).Record();

await sut.Update(_ => "42", CT);
await sut.UpdateAsync(_ => "42", CT);

result.Should().Be(r => r
.Message("0", Progress.Final, Error.No)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Uno.Extensions.Reactive.Testing;

namespace Uno.Extensions.Reactive.Tests.Factories;

[TestClass]
public partial class Given_FeedFactories : FeedTests
{
public class MyService
{
public async ValueTask<MyObject> GetObjectAsync(CancellationToken ct) => default!;
public async ValueTask<MyObject?> GetNullableObjectAsync(CancellationToken ct) => default;

public async ValueTask<MyStruct> GetStructAsync(CancellationToken ct) => default;
public async ValueTask<MyStruct?> GetNullableStructAsync(CancellationToken ct) => default!;

public async ValueTask<string> GetStringAsync(CancellationToken ct) => default!;
public async ValueTask<string?> GetNullableStringAsync(CancellationToken ct) => default!;

public async ValueTask<int> GetIntAsync(CancellationToken ct) => default;
public async ValueTask<int?> GetNullableIntAsync(CancellationToken ct) => default;

public async IAsyncEnumerable<MyObject> GetObjectAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default!; }
public async IAsyncEnumerable<MyObject?> GetNullableObjectAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default; }

public async IAsyncEnumerable<MyStruct> GetStructAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default; }
public async IAsyncEnumerable<MyStruct?> GetNullableStructAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default!; }

public async IAsyncEnumerable<string> GetStringAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default!; }
public async IAsyncEnumerable<string?> GetNullableStringAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default!; }

public async IAsyncEnumerable<int> GetIntAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default; }
public async IAsyncEnumerable<int?> GetNullableIntAsyncEnumerable([EnumeratorCancellation] CancellationToken ct) { yield return default; }
}

public record MyObject;
public record struct MyStruct;

[TestMethod]
public async Task When_AsyncReturnNull_Then_TreatAsNone()
{
// # Typed infer builders
// ## Method group
await Feed.Async(new MyService().GetObjectAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(new MyService().GetStructAsync).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await Feed.Async(new MyService().GetStringAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(new MyService().GetIntAsync).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await Feed.Async(new TestService().GetNullableObject).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(new MyService().GetNullableStructAsync).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy
//await Feed.Async(new TestService().GetNullableString).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(new MyService().GetNullableIntAsync).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy

// ## Lambdas
await Feed.Async(async _ => default(MyObject)!).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(async _ => default(MyStruct)).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await Feed.Async(async _ => default(string)!).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(async _ => default(int)).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await Feed.Async(async _ => default(MyObject?)).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(async _ => default(MyStruct?)).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy
//await Feed.Async(async _ => default(string?)).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.Async(async _ => default(int?)).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy

// # Generic builders
// ## Method group
await Feed<MyObject>.Async(new MyService().GetObjectAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<MyStruct>.Async(new MyService().GetStructAsync).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await Feed<string>.Async(new MyService().GetStringAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<int>.Async(new MyService().GetIntAsync).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await Feed<MyObject>.Async(new TestService().GetNullableObject).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<MyStruct>.Async(new TestService().GetNullableStruct).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<string>.Async(new TestService().GetNullableString).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<int>.Async(new TestService().GetNullableInt).Record().Should().BeAsync(m => m.Message(Data.None));

//await Feed<MyObject?>.Async(new TestService().GetObject).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<MyStruct?>.Async(new TestService().GetStruct).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));;
//await Feed<string?>.Async(new TestService().GetString).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<int?>.Async(new TestService().GetInt).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));;
await Feed<MyObject?>.Async(new MyService().GetNullableObjectAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<MyStruct?>.Async(new MyService().GetNullableStructAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<string?>.Async(new MyService().GetNullableStringAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<int?>.Async(new MyService().GetNullableIntAsync).Record().Should().BeAsync(m => m.Message(Data.None));

// ## Lambdas
await Feed<MyObject>.Async(async _ => default(MyObject)!).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<MyStruct>.Async(async _ => default(MyStruct)).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await Feed<string>.Async(async _ => default(string)!).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<int>.Async(async _ => default(int)).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await Feed<MyObject>.Async(async _ => default(MyObject?)).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<MyStruct>.Async(async _ => default(MyStruct?)).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<string>.Async(async _ => default(string?)).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<int>.Async(async _ => default(int?)).Record().Should().BeAsync(m => m.Message(Data.None));

//await Feed<MyObject?>.Async(async _ => default(MyObject)).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<MyStruct?>.Async(async _ => default(MyStruct)).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
//await Feed<string?>.Async(async _ => default(string)).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<int?>.Async(async _ => default(int)).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
await Feed<MyObject?>.Async(async _ => default(MyObject?)).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<MyStruct?>.Async(async _ => default(MyStruct?)).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<string?>.Async(async _ => default(string?)).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<int?>.Async(async _ => default(int?)).Record().Should().BeAsync(m => m.Message(Data.None));
}

[TestMethod]
public async Task When_AsyncEnumerableReturnNull_Then_TreatAsNone()
{
// # Typed infer builders
// ## Method group
await Feed.AsyncEnumerable(new MyService().GetObjectAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.AsyncEnumerable(new MyService().GetStructAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await Feed.AsyncEnumerable(new MyService().GetStringAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.AsyncEnumerable(new MyService().GetIntAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await Feed.AsyncEnumerable(new TestService().GetNullableObject).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.AsyncEnumerable(new MyService().GetNullableStructAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy
//await Feed.AsyncEnumerable(new TestService().GetNullableString).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed.AsyncEnumerable(new MyService().GetNullableIntAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy

// # Generic builders
// ## Method group
await Feed<MyObject>.AsyncEnumerable(new MyService().GetObjectAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<MyStruct>.AsyncEnumerable(new MyService().GetStructAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await Feed<string>.AsyncEnumerable(new MyService().GetStringAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<int>.AsyncEnumerable(new MyService().GetIntAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await Feed<MyObject>.AsyncEnumerable(new TestService().GetNullableObject).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<MyStruct>.AsyncEnumerable(new TestService().GetNullableStruct).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<string>.AsyncEnumerable(new TestService().GetNullableString).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<int>.AsyncEnumerable(new TestService().GetNullableInt).Record().Should().BeAsync(m => m.Message(Data.None));

//await Feed<MyObject?>.AsyncEnumerable(new TestService().GetObject).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<MyStruct?>.AsyncEnumerable(new TestService().GetStruct).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));;
//await Feed<string?>.AsyncEnumerable(new TestService().GetString).Record().Should().BeAsync(m => m.Message(Data.None));
//await Feed<int?>.AsyncEnumerable(new TestService().GetInt).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));;
await Feed<MyObject?>.AsyncEnumerable(new MyService().GetNullableObjectAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<MyStruct?>.AsyncEnumerable(new MyService().GetNullableStructAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<string?>.AsyncEnumerable(new MyService().GetNullableStringAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
await Feed<int?>.AsyncEnumerable(new MyService().GetNullableIntAsyncEnumerable).Record().Should().BeAsync(m => m.Message(Data.None));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public async Task When_UpdateToEmptyList_Then_GoesToNone()
.Current(Items.Range(20), Error.No, Progress.Final))
);

await sut.Update(_ => ImmutableList<int>.Empty, CT);
await sut.UpdateAsync(_ => ImmutableList<int>.Empty, CT);

result.Should().Be(b => b
.Message(m => m
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Linq;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Uno.Extensions.Reactive.Testing;

namespace Uno.Extensions.Reactive.Tests.Factories;

[TestClass]
public class Given_SelectAsyncFactories : FeedTests
{
public class MyService
{
public async ValueTask<MyObject> GetObjectAsync<T>(T t, CancellationToken ct) => default!;
public async ValueTask<MyObject?> GetNullableObjectAsync<T>(T t, CancellationToken ct) => default;

public async ValueTask<MyStruct> GetStructAsync<T>(T t, CancellationToken ct) => default;
public async ValueTask<MyStruct?> GetNullableStructAsync<T>(T t, CancellationToken ct) => default!;

public async ValueTask<string> GetStringAsync<T>(T t, CancellationToken ct) => default!;
public async ValueTask<string?> GetNullableStringAsync<T>(T t, CancellationToken ct) => default!;

public async ValueTask<int> GetIntAsync<T>(T t, CancellationToken ct) => default;
public async ValueTask<int?> GetNullableIntAsync<T>(T t, CancellationToken ct) => default;
}

public record MyObject;
public record struct MyStruct;

[TestMethod] public Task When_FeedOfObjectSelectAsyncReturnNull_Then_TreatAsNone() => When_SelectAsyncReturnNull_Then_TreatAsNone(Feed.Async(async _ => new MyObject()));
[TestMethod] public Task When_FeedOfStructSelectAsyncReturnNull_Then_TreatAsNone() => When_SelectAsyncReturnNull_Then_TreatAsNone(Feed.Async(async _ => new MyStruct()));
[TestMethod] public Task When_FeedOfStringSelectAsyncReturnNull_Then_TreatAsNone() => When_SelectAsyncReturnNull_Then_TreatAsNone(Feed.Async(async _ => ""));
[TestMethod] public Task When_FeedOfIntSelectAsyncReturnNull_Then_TreatAsNone() => When_SelectAsyncReturnNull_Then_TreatAsNone(Feed.Async(async _ => 42));

public async Task When_SelectAsyncReturnNull_Then_TreatAsNone<T>(IFeed<T> feed)
{
// # Typed infer builders
// ## Method group
await feed.SelectAsync(new MyService().GetObjectAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(new MyService().GetStructAsync).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await feed.SelectAsync(new MyService().GetStringAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(new MyService().GetIntAsync).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await feed.SelectAsync(new TestService().GetNullableObjectAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(new MyService().GetNullableStructAsync).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy
//await feed.SelectAsync(new TestService().GetNullableStringAsync).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(new MyService().GetNullableIntAsync).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy

// ## Lambdas
await feed.SelectAsync(async (_, ct) => default(MyObject)!).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(async (_, ct) => default(MyStruct)).Record().Should().BeAsync(m => m.Message(Option.Some(default(MyStruct))));
await feed.SelectAsync(async (_, ct) => default(string)!).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(async (_, ct) => default(int)).Record().Should().BeAsync(m => m.Message(Option.Some(default(int))));
//await feed.SelectAsync(async (_, ct) => default(MyObject?)).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(async (_, ct) => default(MyStruct?)).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy
//await feed.SelectAsync(async (_, ct) => default(string?)).Record().Should().BeAsync(m => m.Message(Data.None));
await feed.SelectAsync(async (_, ct) => default(int?)).Record().Should().BeAsync(m => m.Message(Data.None)); // Candy
}
}
Loading
Loading