Created
September 2, 2017 02:57
-
-
Save mikecann/c9a1be8e93614e0d2526dee5cb80f9b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using RSG; | |
using UniRx; | |
namespace PromiseReactiveCommand | |
{ | |
public interface IBasePromiseReactiveCommand | |
{ | |
IReadOnlyReactiveProperty<bool> CanExecute { get; } | |
} | |
public abstract class BasePromiseReactiveCommand : IBasePromiseReactiveCommand | |
{ | |
protected readonly IReactiveProperty<bool> _canExecuteSource; | |
protected readonly IReactiveProperty<bool> _isExecuting; | |
protected BasePromiseReactiveCommand() | |
: this(new BoolReactiveProperty(true)) | |
{ | |
} | |
protected BasePromiseReactiveCommand(IObservable<bool> canExecuteSource) | |
{ | |
_canExecuteSource = canExecuteSource.ToReactiveProperty(); | |
_isExecuting = new BoolReactiveProperty(false); | |
CanExecute = _isExecuting.CombineLatest(canExecuteSource, (x, y) => !x && y) | |
.ToReactiveProperty(); | |
} | |
protected IPromise Execute(Func<IPromise> func) | |
{ | |
if (!CanExecute.Value) | |
return Promise.Rejected(new CannotExecuteException()); | |
_isExecuting.Value = true; | |
return func() | |
.Then(() => _isExecuting.Value = false) | |
.Catch(e => _isExecuting.Value = false); | |
} | |
public IReadOnlyReactiveProperty<bool> CanExecute { get; private set; } | |
public class CannotExecuteException : Exception | |
{ | |
public CannotExecuteException() | |
: base("Cannot execute, not ready.") | |
{ | |
} | |
} | |
} | |
public interface IPromiseReactiveCommand<T> : IBasePromiseReactiveCommand | |
{ | |
IPromise Execute(T parameter); | |
IDisposable Subscribe(Func<T, IPromise> execHandler); | |
} | |
public class PromiseReactiveCommand<T> : BasePromiseReactiveCommand, IPromiseReactiveCommand<T> | |
{ | |
private List<Func<T, IPromise>> _handlers; | |
public PromiseReactiveCommand() | |
: base() | |
{ | |
_handlers = new List<Func<T, IPromise>>(); | |
} | |
public PromiseReactiveCommand(IObservable<bool> canExecuteSource) | |
: base(canExecuteSource) | |
{ | |
_handlers = new List<Func<T, IPromise>>(); | |
} | |
public IPromise Execute(T parameter) | |
{ | |
return Execute(() => Promise.All(_handlers.Select(h => h(parameter)))); | |
} | |
public IDisposable Subscribe(Func<T, IPromise> execHandler) | |
{ | |
_handlers.Add(execHandler); | |
return Disposable.Create(() => _handlers.Remove(execHandler)); | |
} | |
} | |
public interface IPromiseReactiveCommand : IBasePromiseReactiveCommand | |
{ | |
IPromise Execute(); | |
IDisposable Subscribe(Func<IPromise> execHandler); | |
} | |
public class PromiseReactiveCommand : BasePromiseReactiveCommand, IPromiseReactiveCommand | |
{ | |
private List<Func<IPromise>> _handlers; | |
public PromiseReactiveCommand() | |
: base() | |
{ | |
_handlers = new List<Func<IPromise>>(); | |
} | |
public PromiseReactiveCommand(IObservable<bool> canExecuteSource) | |
: base(canExecuteSource) | |
{ | |
_handlers = new List<Func<IPromise>>(); | |
} | |
public IPromise Execute() | |
{ | |
return Execute(() => Promise.All(_handlers.Select(h => h()))); | |
} | |
public IDisposable Subscribe(Func<IPromise> execHandler) | |
{ | |
_handlers.Add(execHandler); | |
return Disposable.Create(() => _handlers.Remove(execHandler)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Linq; | |
using NUnit.Framework; | |
using RSG; | |
using UniRx; | |
namespace PromiseReactiveCommand | |
{ | |
public class PromiseReactiveCommandTests | |
{ | |
private PromiseReactiveCommand _command; | |
[SetUp] | |
public void Setup() | |
{ | |
_command = new PromiseReactiveCommand(); | |
} | |
[Test] | |
public void WhenConstructed_CanExecute() | |
{ | |
Assert.IsTrue(_command.CanExecute.Value); | |
} | |
[Test] | |
public void WhenExecuting_CannotExecute() | |
{ | |
var promise = new Promise(); | |
_command.Subscribe(() => promise); | |
Assert.IsTrue(_command.CanExecute.Value); | |
_command.Execute(); | |
Assert.IsFalse(_command.CanExecute.Value); | |
} | |
[Test] | |
public void WhenExecutingAndCannotExecute_CommandPromiseRejects() | |
{ | |
var promise = new Promise(); | |
_command.Subscribe(() => promise); | |
Assert.IsTrue(_command.CanExecute.Value); | |
_command.Execute(); | |
var commandPromise = _command.Execute(); | |
commandPromise.AssertPromiseIsRejected(typeof(BasePromiseReactiveCommand.CannotExecuteException)); | |
} | |
[Test] | |
public void WhenExecuted_CanExecuteAgain() | |
{ | |
var promise = new Promise(); | |
_command.Subscribe(() => promise); | |
_command.Execute(); | |
promise.Resolve(); | |
Assert.IsTrue(_command.CanExecute.Value); | |
} | |
[Test] | |
public void WhenExecting_OnlyCanExecuteAgainWhenAllSubscribersAreDone() | |
{ | |
var promises = Enumerable.Range(0, 10).Select(i => new Promise()).ToArray(); | |
foreach (var promise in promises) | |
_command.Subscribe(() => promise); | |
_command.Execute(); | |
promises[0].Resolve(); | |
Assert.IsFalse(_command.CanExecute.Value); | |
promises | |
.Where(p => p.CurState == PromiseState.Pending) | |
.ForEach(p => p.Resolve()); | |
Assert.IsTrue(_command.CanExecute.Value); | |
} | |
[Test] | |
public void WhenExecting_CommandExecutePromiseOnlyReturnsTrueWhenDone() | |
{ | |
var promises = Enumerable.Range(0, 10).Select(i => new Promise()).ToArray(); | |
foreach (var promise in promises) | |
_command.Subscribe(() => promise); | |
var commandPromise = _command.Execute(); | |
commandPromise.AssertPromiseIsNotResolvedOrRejected(); | |
promises.ForEach(p => p.Resolve()); | |
commandPromise.AssertPromiseIsResolved(); | |
} | |
[Test] | |
public void DisposingSubscription_EnsuresSubscriptionIsNotRun() | |
{ | |
var count = 0; | |
var promises = Enumerable.Range(0, 10).Select(i => new Promise()).ToArray(); | |
var disposables = promises.Select(p => _command.Subscribe(() => { | |
count++; | |
return p; | |
})).ToArray(); | |
disposables[4].Dispose(); | |
_command.Execute(); | |
Assert.AreEqual(9, count); | |
} | |
[Test] | |
public void IfSuppliedCanExecuteObservalbeIsFalse_CannotExecute() | |
{ | |
var promise = new Promise(); | |
var canExecSource = new BoolReactiveProperty(false); | |
_command = new PromiseReactiveCommand(canExecSource); | |
_command.Subscribe(() => promise); | |
Assert.IsFalse(_command.CanExecute.Value); | |
} | |
[Test] | |
public void IfSuppliedCanExecuteObservalbeTurnsTrue_CanExecute() | |
{ | |
var promise = new Promise(); | |
var canExecSource = new BoolReactiveProperty(false); | |
_command = new PromiseReactiveCommand(canExecSource); | |
_command.Subscribe(() => promise); | |
Assert.IsFalse(_command.CanExecute.Value); | |
canExecSource.Value = true; | |
Assert.IsTrue(_command.CanExecute.Value); | |
} | |
[Test] | |
public void IfSuppliedCanExecuteObservalbeTurnsFalseDuringExecuting_CanExecuteFalseEvenWhenComplete() | |
{ | |
var promise = new Promise(); | |
var canExecSource = new BoolReactiveProperty(false); | |
_command = new PromiseReactiveCommand(canExecSource); | |
_command.Subscribe(() => promise); | |
canExecSource.Value = true; | |
_command.Execute().Done(); | |
Assert.IsFalse(_command.CanExecute.Value); | |
canExecSource.Value = false; | |
Assert.IsFalse(_command.CanExecute.Value); | |
canExecSource.Value = true; | |
Assert.IsFalse(_command.CanExecute.Value); | |
canExecSource.Value = false; | |
Assert.IsFalse(_command.CanExecute.Value); | |
promise.Resolve(); | |
Assert.IsFalse(_command.CanExecute.Value); | |
canExecSource.Value = true; | |
Assert.IsTrue(_command.CanExecute.Value); | |
} | |
[Test] | |
public void WhenExecutingAndCannotExecute_CommandPromiseRejectsButCanBeExecutedAgainIfRequirementsChange() | |
{ | |
var promise = new Promise(); | |
var canExecSource = new BoolReactiveProperty(false); | |
_command = new PromiseReactiveCommand(canExecSource); | |
_command.Subscribe(() => promise); | |
Assert.IsFalse(_command.CanExecute.Value); | |
var commandPromise = _command.Execute(); | |
commandPromise.AssertPromiseIsRejected(typeof(BasePromiseReactiveCommand.CannotExecuteException)); | |
Assert.IsFalse(_command.CanExecute.Value); | |
canExecSource.Value = true; | |
Assert.IsTrue(_command.CanExecute.Value); | |
_command.Execute(); | |
Assert.IsFalse(_command.CanExecute.Value); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment