Skip to content

Commit

Permalink
Feature ReactiveCommand.CreateRunInBackground (#3501)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisPulman committed May 11, 2023
1 parent 42929d4 commit 06459fc
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace ReactiveUI
}
public class CombinedReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, System.Collections.Generic.IList<TResult>>
{
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool> canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public override System.IObservable<bool> CanExecute { get; }
public override System.IObservable<bool> IsExecuting { get; }
public override System.IObservable<System.Exception> ThrownExceptions { get; }
Expand Down Expand Up @@ -638,6 +638,10 @@ namespace ReactiveUI
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateFromTask<TParam>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> CreateRunInBackground(System.Action execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateRunInBackground<TParam>(System.Action<TParam> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, TResult> CreateRunInBackground<TResult>(System.Func<TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateRunInBackground<TParam, TResult>(System.Func<TParam, TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
}
public abstract class ReactiveCommandBase<TParam, TResult> : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable<TResult>, System.Windows.Input.ICommand
{
Expand Down Expand Up @@ -665,7 +669,7 @@ namespace ReactiveUI
}
public class ReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, TResult>
{
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { }
public override System.IObservable<bool> CanExecute { get; }
public override System.IObservable<bool> IsExecuting { get; }
public override System.IObservable<System.Exception> ThrownExceptions { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace ReactiveUI
}
public class CombinedReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, System.Collections.Generic.IList<TResult>>
{
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool> canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public override System.IObservable<bool> CanExecute { get; }
public override System.IObservable<bool> IsExecuting { get; }
public override System.IObservable<System.Exception> ThrownExceptions { get; }
Expand Down Expand Up @@ -645,6 +645,10 @@ namespace ReactiveUI
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateFromTask<TParam>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> CreateRunInBackground(System.Action execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateRunInBackground<TParam>(System.Action<TParam> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, TResult> CreateRunInBackground<TResult>(System.Func<TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateRunInBackground<TParam, TResult>(System.Func<TParam, TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
}
public abstract class ReactiveCommandBase<TParam, TResult> : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable<TResult>, System.Windows.Input.ICommand
{
Expand Down Expand Up @@ -672,7 +676,7 @@ namespace ReactiveUI
}
public class ReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, TResult>
{
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { }
public override System.IObservable<bool> CanExecute { get; }
public override System.IObservable<bool> IsExecuting { get; }
public override System.IObservable<System.Exception> ThrownExceptions { get; }
Expand Down
8 changes: 5 additions & 3 deletions src/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using DynamicData;
using Microsoft.Reactive.Testing;
using ReactiveUI.Testing;
Expand Down Expand Up @@ -104,7 +105,6 @@ public void CanExecuteTicksFailuresThroughThrownExceptions()
Exception? exception = null;
fixture.ThrownExceptions.Subscribe(ex => exception = ex);
fixture.Execute().Subscribe(_ => { }, _ => { });
Assert.Null(exception);
scheduler.Start();
Assert.IsType<InvalidOperationException>(exception);
Expand Down Expand Up @@ -194,11 +194,12 @@ public void ExecuteTicksThroughTheResults()
/// </summary>
[Fact]
public void ResultIsTickedThroughSpecifiedScheduler() =>
new TestScheduler().With(
new TestScheduler().WithAsync(
scheduler =>
{
// Allow scheduler to run freely
var child1 = ReactiveCommand.Create(() => Observable.Return(1));
var child2 = ReactiveCommand.Create(() => Observable.Return(2));
var child2 = ReactiveCommand.CreateRunInBackground(() => Observable.Return(2));
var childCommands = new[] { child1, child2 };
var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler);
fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe();
Expand All @@ -208,6 +209,7 @@ public void ExecuteTicksThroughTheResults()
scheduler.AdvanceByMs(1);
Assert.Equal(1, results.Count);
return Task.CompletedTask;
});
}
}
25 changes: 23 additions & 2 deletions src/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,26 @@ public void CreateThrowsIfExecutionParameterIsNull()
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}

/// <summary>
/// Creates the throws if execution parameter is null.
/// </summary>
[Fact]
public void CreateRunInBackgroundThrowsIfExecutionParameterIsNull()
{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground(null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit>)null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Action<Unit>)null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit, Unit>)null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<IObservable<Unit>>)null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Task<Unit>>)null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit, IObservable<Unit>>)null));
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit, Task<Unit>>)null));
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
}

/// <summary>
/// Exceptions the are delivered on output scheduler.
/// </summary>
Expand Down Expand Up @@ -1059,17 +1079,18 @@ public void IsExecutingRemainsTrueAsLongAsExecutionPipelineHasNotCompleted()
/// </summary>
[Fact]
public void ResultIsTickedThroughSpecifiedScheduler() =>
new TestScheduler().With(
new TestScheduler().WithAsync(
scheduler =>
{
var fixture = ReactiveCommand.Create(() => Observables.Unit, outputScheduler: scheduler);
var fixture = ReactiveCommand.CreateRunInBackground(() => Observables.Unit, outputScheduler: scheduler);
fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe();
fixture.Execute().Subscribe();
Assert.Empty(results);
scheduler.AdvanceByMs(1);
Assert.Equal(1, results.Count);
return Task.CompletedTask;
});

/// <summary>
Expand Down
20 changes: 10 additions & 10 deletions src/ReactiveUI.Tests/Mocks/MockBindListViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using DynamicData;

Expand All @@ -27,17 +28,16 @@ static MockBindListViewModel()
/// </summary>
public MockBindListViewModel()
{
SelectItem = ReactiveCommand.Create((MockBindListItemViewModel item) =>
{
ActiveListItem.Edit(l =>
{
var index = l.IndexOf(item);
for (var i = l.Count - 1; i > index; i--)
SelectItem = ReactiveCommand.Create(
(MockBindListItemViewModel item) =>
ActiveListItem.Edit(l =>
{
l.RemoveAt(i);
}
});
});
var index = l.IndexOf(item);
for (var i = l.Count - 1; i > index; i--)
{
l.RemoveAt(i);
}
}));

ActiveListItem.Connect()
.Select(_ => ActiveListItem.Count > 0 ? ActiveListItem.Items.ElementAt(ActiveListItem.Count - 1) : null)
Expand Down
14 changes: 8 additions & 6 deletions src/ReactiveUI.Tests/Platforms/winforms/CommandBindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using System.Windows.Forms;
using ReactiveUI.Winforms;
using Xunit;
Expand All @@ -20,12 +21,13 @@ public class CommandBindingTests
/// <summary>
/// Tests that the command binder binds to button.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public void CommandBinderBindsToButton()
public async Task CommandBinderBindsToButtonAsync()
{
var fixture = new CreatesWinformsCommandBinding();
var cmd = ReactiveCommand.Create<int>(_ => { });
var input = new Button { };
var cmd = ReactiveCommand.CreateRunInBackground<int>(_ => { });
var input = new Button();

Assert.True(fixture.GetAffinityForObject(input.GetType(), true) > 0);
Assert.True(fixture.GetAffinityForObject(input.GetType(), false) > 0);
Expand All @@ -40,7 +42,7 @@ public void CommandBinderBindsToButton()
using (fixture.BindCommandToObject(cmd, input, Observable.Return((object)5)))
{
input.PerformClick();

await Task.Delay(10);
Assert.True(commandExecuted);
Assert.NotNull(ea);
}
Expand All @@ -54,7 +56,7 @@ public void CommandBinderBindsToCustomControl()
{
var fixture = new CreatesWinformsCommandBinding();
var cmd = ReactiveCommand.Create<int>(_ => { });
var input = new CustomClickableControl { };
var input = new CustomClickableControl();

Assert.True(fixture.GetAffinityForObject(input.GetType(), true) > 0);
Assert.True(fixture.GetAffinityForObject(input.GetType(), false) > 0);
Expand Down Expand Up @@ -83,7 +85,7 @@ public void CommandBinderBindsToCustomComponent()
{
var fixture = new CreatesWinformsCommandBinding();
var cmd = ReactiveCommand.Create<int>(_ => { });
var input = new CustomClickableComponent { };
var input = new CustomClickableComponent();

Assert.True(fixture.GetAffinityForObject(input.GetType(), true) > 0);
Assert.True(fixture.GetAffinityForObject(input.GetType(), false) > 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class WinformCommandBindViewModel : ReactiveObject
public WinformCommandBindViewModel()
{
_command1 = ReactiveCommand.Create(() => { }, outputScheduler: ImmediateScheduler.Instance);
_command2 = ReactiveCommand.Create(() => { }, outputScheduler: ImmediateScheduler.Instance);
_command2 = ReactiveCommand.CreateRunInBackground(() => { }, outputScheduler: ImmediateScheduler.Instance);
_command3 = ReactiveCommand.Create<int>(i => ParameterResult = i * 10, outputScheduler: ImmediateScheduler.Instance);
}

Expand Down

0 comments on commit 06459fc

Please sign in to comment.