Skip to content

Instantly share code, notes, and snippets.

@mikecann
Created September 2, 2017 02:57
Show Gist options
  • Save mikecann/c9a1be8e93614e0d2526dee5cb80f9b7 to your computer and use it in GitHub Desktop.
Save mikecann/c9a1be8e93614e0d2526dee5cb80f9b7 to your computer and use it in GitHub Desktop.
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));
}
}
}
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