Skip to content

Commit

Permalink
Merge 53b470b into e41af92
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcb committed Feb 1, 2017
2 parents e41af92 + 53b470b commit 325a7c1
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 3 deletions.
85 changes: 83 additions & 2 deletions src/ReactiveUI.Tests/InteractionsTest.cs
Expand Up @@ -2,22 +2,65 @@
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Microsoft.Reactive.Testing;
using ReactiveUI.Testing;
using Xunit;

namespace ReactiveUI.Tests
{
public class InteractionsTest
{
[Fact]
public void RegisterNullHandlerShouldCauseException()
{
var interaction = new Interaction<Unit, Unit>();

Assert.Throws<ArgumentNullException>(() => interaction.RegisterHandler((Action<InteractionContext<Unit, Unit>>)null));
Assert.Throws<ArgumentNullException>(() => interaction.RegisterHandler((Func<InteractionContext<Unit, Unit>, Task>)null));
Assert.Throws<ArgumentNullException>(() => interaction.RegisterHandler((Func<InteractionContext<Unit, Unit>, IObservable<Unit>>)null));
}

[Fact]
public void UnhandledInteractionsShouldCauseException()
{
var interaction = new Interaction<string, Unit>();
Assert.Throws<UnhandledInteractionException<string, Unit>>(() => interaction.Handle("foo").FirstAsync().Wait());
var ex = Assert.Throws<UnhandledInteractionException<string, Unit>>(() => interaction.Handle("foo").FirstAsync().Wait());
Assert.Same(interaction, ex.Interaction);
Assert.Equal("foo", ex.Input);

interaction.RegisterHandler(_ => { });
interaction.RegisterHandler(_ => { });
Assert.Throws<UnhandledInteractionException<string, Unit>>(() => interaction.Handle("foo").FirstAsync().Wait());
ex = Assert.Throws<UnhandledInteractionException<string, Unit>>(() => interaction.Handle("bar").FirstAsync().Wait());
Assert.Same(interaction, ex.Interaction);
Assert.Equal("bar", ex.Input);
}

[Fact]
public void AttemptingToSetInteractionOutputMoreThanOnceShouldCauseException()
{
var interaction = new Interaction<Unit, Unit>();

interaction.RegisterHandler(context => {
context.SetOutput(Unit.Default);
context.SetOutput(Unit.Default);
});

var ex = Assert.Throws<InvalidOperationException>(() => interaction.Handle(Unit.Default).Subscribe());
Assert.Equal("Output has already been set.", ex.Message);
}

[Fact]
public void AttemptingToGetInteractionOutputBeforeItHasBeenSetShouldCauseException()
{
var interaction = new Interaction<Unit, Unit>();

interaction.RegisterHandler(context => {
var output = context.GetOutput();
});

var ex = Assert.Throws<InvalidOperationException>(() => interaction.Handle(Unit.Default).Subscribe());
Assert.Equal("Output has not been set.", ex.Message);
}

[Fact]
Expand All @@ -29,6 +72,26 @@ public void HandledInteractionsShouldNotCauseException()
interaction.Handle(Unit.Default).FirstAsync().Wait();
}

[Fact]
public void HandlersAreExecutedOnHandlerScheduler()
{
(new TestScheduler()).With(sched => {
var interaction = new Interaction<Unit, string>(sched);
using (interaction.RegisterHandler(x => x.SetOutput("done"))) {
var handled = false;
interaction
.Handle(Unit.Default)
.Subscribe(_ => handled = true);
Assert.False(handled);
sched.Start();
Assert.True(handled);
}
});
}

[Fact]
public void NestedHandlersAreExecutedInReverseOrderOfSubscription()
{
Expand Down Expand Up @@ -118,5 +181,23 @@ public void HandlersCanContainAsynchronousCode()

Assert.False(handler1AWasCalled);
}

[Fact]
public void HandlersCanContainAsynchronousCodeViaTasks()
{
var interaction = new Interaction<Unit, string>();

interaction.RegisterHandler(context => {
context.SetOutput("result");
return Task.FromResult(true);
});

string result = null;
interaction
.Handle(Unit.Default)
.Subscribe(r => result = r);

Assert.Equal("result", result);
}
}
}
12 changes: 11 additions & 1 deletion src/ReactiveUI/Interactions.cs
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
Expand Down Expand Up @@ -127,11 +128,19 @@ public class Interaction<TInput, TOutput>
{
private readonly IList<Func<InteractionContext<TInput, TOutput>, IObservable<Unit>>> handlers;
private readonly object sync;
private readonly IScheduler handlerScheduler;

public Interaction()
/// <summary>
/// Creates a new interaction instance.
/// </summary>
/// <param name="handlerScheduler">
/// The scheduler to use when invoking handlers, which defaults to <c>CurrentThreadScheduler.Instance</c> if <see langword="null"/>.
/// </param>
public Interaction(IScheduler handlerScheduler = null)
{
this.handlers = new List<Func<InteractionContext<TInput, TOutput>, IObservable<Unit>>>();
this.sync = new object();
this.handlerScheduler = handlerScheduler ?? CurrentThreadScheduler.Instance;
}

/// <summary>
Expand Down Expand Up @@ -235,6 +244,7 @@ public virtual IObservable<TOutput> Handle(TInput input)
.GetHandlers()
.Reverse()
.ToObservable()
.ObserveOn(this.handlerScheduler)
.Select(handler => Observable.Defer(() => handler(context)))
.Concat()
.TakeWhile(_ => !context.IsHandled)
Expand Down

0 comments on commit 325a7c1

Please sign in to comment.