Browse Source

Merged changes from master.

pull/4/head
Steven Kirk 12 years ago
parent
commit
edab64cfdf
  1. 3
      Perspex.UnitTests/Perspex.UnitTests.csproj
  2. 200
      Perspex.UnitTests/Styling/ActivatorTests.cs
  3. 65
      Perspex.UnitTests/TestObserver.cs
  4. 60
      Perspex.UnitTests/TestSubject.cs
  5. 56
      Perspex/Styling/Activator.cs
  6. 15
      Perspex/Styling/Selector.cs

3
Perspex.UnitTests/Perspex.UnitTests.csproj

@ -72,6 +72,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PerspexObjectTests.cs" />
<Compile Include="StyleTests.cs" />
<Compile Include="Styling\ActivatorTests.cs" />
<Compile Include="Styling\SelectorTests_Class.cs" />
<Compile Include="Styling\SelectorTests_Id.cs" />
<Compile Include="Styling\SelectorTests_OfType.cs" />
@ -82,6 +83,8 @@
<Compile Include="Styling\TestSelectors.cs" />
<Compile Include="Styling\TestObservable.cs" />
<Compile Include="Styling\TestControlBase.cs" />
<Compile Include="TestSubject.cs" />
<Compile Include="TestObserver.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Perspex\Perspex.csproj">

200
Perspex.UnitTests/Styling/ActivatorTests.cs

@ -0,0 +1,200 @@
namespace Perspex.UnitTests.Styling
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Perspex.Styling;
using Activator = Perspex.Styling.Activator;
[TestClass]
public class ActivatorTests
{
[TestMethod]
public void Activator_And_Should_Follow_Single_Input()
{
var inputs = new[] { new TestSubject<bool>(false) };
var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
Assert.IsTrue(result.GetValue());
inputs[0].OnNext(false);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
Assert.IsTrue(result.GetValue());
Assert.AreEqual(1, inputs[0].SubscriberCount);
}
[TestMethod]
public void Activator_And_Should_AND_Multiple_Inputs()
{
var inputs = new[]
{
new TestSubject<bool>(false),
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
inputs[1].OnNext(true);
Assert.IsTrue(result.GetValue());
inputs[0].OnNext(false);
Assert.IsFalse(result.GetValue());
Assert.AreEqual(1, inputs[0].SubscriberCount);
Assert.AreEqual(1, inputs[1].SubscriberCount);
Assert.AreEqual(1, inputs[2].SubscriberCount);
}
[TestMethod]
public void Activator_And_Should_Unsubscribe_All_When_Input_Completes_On_False()
{
var inputs = new[]
{
new TestSubject<bool>(false),
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
inputs[1].OnNext(true);
Assert.IsTrue(result.GetValue());
inputs[0].OnNext(false);
Assert.IsFalse(result.GetValue());
inputs[0].OnCompleted();
Assert.AreEqual(0, inputs[0].SubscriberCount);
Assert.AreEqual(0, inputs[1].SubscriberCount);
Assert.AreEqual(0, inputs[2].SubscriberCount);
}
[TestMethod]
public void Activator_And_Should_Not_Unsubscribe_All_When_Input_Completes_On_True()
{
var inputs = new[]
{
new TestSubject<bool>(false),
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.And);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
inputs[0].OnCompleted();
Assert.AreEqual(1, inputs[0].SubscriberCount);
Assert.AreEqual(1, inputs[1].SubscriberCount);
Assert.AreEqual(1, inputs[2].SubscriberCount);
}
[TestMethod]
public void Activator_Or_Should_Follow_Single_Input()
{
var inputs = new[] { new TestSubject<bool>(false) };
var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
Assert.IsTrue(result.GetValue());
inputs[0].OnNext(false);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
Assert.IsTrue(result.GetValue());
Assert.AreEqual(1, inputs[0].SubscriberCount);
}
[TestMethod]
public void Activator_Or_Should_OR_Multiple_Inputs()
{
var inputs = new[]
{
new TestSubject<bool>(false),
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsTrue(result.GetValue());
inputs[2].OnNext(false);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
Assert.IsTrue(result.GetValue());
Assert.AreEqual(1, inputs[0].SubscriberCount);
Assert.AreEqual(1, inputs[1].SubscriberCount);
Assert.AreEqual(1, inputs[2].SubscriberCount);
}
[TestMethod]
public void Activator_Or_Should_Unsubscribe_All_When_Input_Completes_On_True()
{
var inputs = new[]
{
new TestSubject<bool>(false),
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsTrue(result.GetValue());
inputs[2].OnNext(false);
Assert.IsFalse(result.GetValue());
inputs[0].OnNext(true);
Assert.IsTrue(result.GetValue());
inputs[0].OnCompleted();
Assert.AreEqual(0, inputs[0].SubscriberCount);
Assert.AreEqual(0, inputs[1].SubscriberCount);
Assert.AreEqual(0, inputs[2].SubscriberCount);
}
[TestMethod]
public void Activator_Or_Should_Not_Unsubscribe_All_When_Input_Completes_On_False()
{
var inputs = new[]
{
new TestSubject<bool>(false),
new TestSubject<bool>(false),
new TestSubject<bool>(true),
};
var target = new Activator(inputs, ActivatorMode.Or);
var result = new TestObserver<bool>();
target.Subscribe(result);
Assert.IsTrue(result.GetValue());
inputs[2].OnNext(false);
Assert.IsFalse(result.GetValue());
inputs[2].OnCompleted();
Assert.AreEqual(1, inputs[0].SubscriberCount);
Assert.AreEqual(1, inputs[1].SubscriberCount);
Assert.AreEqual(1, inputs[2].SubscriberCount);
}
}
}

65
Perspex.UnitTests/TestObserver.cs

@ -0,0 +1,65 @@
// -----------------------------------------------------------------------
// <copyright file="TestObserver.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.UnitTests
{
using System;
internal class TestObserver<T> : IObserver<T>
{
private bool hasValue;
private T value;
public bool Completed { get; private set; }
public Exception Error { get; private set; }
public T GetValue()
{
if (!this.hasValue)
{
throw new Exception("Observable provided no value.");
}
if (this.Completed)
{
throw new Exception("Observable completed unexpectedly.");
}
if (this.Error != null)
{
throw new Exception("Observable errored unexpectedly.");
}
this.hasValue = false;
return value;
}
public void OnCompleted()
{
this.Completed = true;
}
public void OnError(Exception error)
{
this.Error = error;
}
public void OnNext(T value)
{
if (!this.hasValue)
{
this.value = value;
this.hasValue = true;
}
else
{
throw new Exception("Observable pushed more than one value.");
}
}
}
}

60
Perspex.UnitTests/TestSubject.cs

@ -0,0 +1,60 @@
// -----------------------------------------------------------------------
// <copyright file="TestSubject.cs" company="Steven Kirk">
// Copyright 2014 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.UnitTests
{
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
internal class TestSubject<T> : IObserver<T>, IObservable<T>
{
private T initial;
private List<IObserver<T>> subscribers = new List<IObserver<T>>();
public TestSubject(T initial)
{
this.initial = initial;
}
public int SubscriberCount
{
get { return this.subscribers.Count; }
}
public void OnCompleted()
{
foreach (IObserver<T> subscriber in this.subscribers.ToArray())
{
subscriber.OnCompleted();
}
}
public void OnError(Exception error)
{
foreach (IObserver<T> subscriber in this.subscribers.ToArray())
{
subscriber.OnError(error);
}
}
public void OnNext(T value)
{
foreach (IObserver<T> subscriber in this.subscribers.ToArray())
{
subscriber.OnNext(value);
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
this.subscribers.Add(observer);
observer.OnNext(initial);
return Disposable.Create(() => this.subscribers.Remove(observer));
}
}
}

56
Perspex/Styling/Activator.cs

@ -11,8 +11,16 @@ namespace Perspex.Styling
using System.Linq;
using System.Reactive.Disposables;
public enum ActivatorMode
{
And,
Or,
}
public class Activator : IObservable<bool>
{
ActivatorMode mode;
List<bool> values = new List<bool>();
List<IDisposable> subscriptions = new List<IDisposable>();
@ -21,27 +29,24 @@ namespace Perspex.Styling
bool last = false;
public Activator(Selector match, IStyleable control)
public Activator(IEnumerable<IObservable<bool>> inputs, ActivatorMode mode = ActivatorMode.And)
{
int i = 0;
while (match != null)
this.mode = mode;
foreach (IObservable<bool> input in inputs)
{
int iCaptured = i;
if (match.Observable != null)
{
this.values.Add(false);
IDisposable subscription = match.Observable(control).Subscribe(
x => this.Update(iCaptured, x),
x => this.Finish(iCaptured),
() => this.Finish(iCaptured));
this.subscriptions.Add(subscription);
++i;
}
this.values.Add(false);
match = match.Previous;
IDisposable subscription = input.Subscribe(
x => this.Update(iCaptured, x),
x => this.Finish(iCaptured),
() => this.Finish(iCaptured));
this.subscriptions.Add(subscription);
++i;
}
}
@ -58,7 +63,19 @@ namespace Perspex.Styling
{
this.values[index] = value;
bool current = this.values.All(x => x);
bool current;
switch (this.mode)
{
case ActivatorMode.And:
current = this.values.All(x => x);
break;
case ActivatorMode.Or:
current = this.values.Any(x => x);
break;
default:
throw new InvalidOperationException("Invalid Activator mode.");
}
if (current != last)
{
@ -69,10 +86,13 @@ namespace Perspex.Styling
private void Finish(int i)
{
if (!this.values[i])
// If the observable has finished on 'false' and we're in And mode then it will never
// go back to true so we can unsubscribe from all the other subscriptions now.
// Similarly in Or mode; if the completed value is true then we're done.
bool unsubscribe = this.mode == ActivatorMode.And ? !this.values[i] : this.values[i];
if (unsubscribe)
{
// If the observable has finished on 'false' then it will never go back to true
// so we can unsubscribe from all the other subscriptions now.
foreach (IDisposable subscription in this.subscriptions)
{
subscription.Dispose();

15
Perspex/Styling/Selector.cs

@ -53,7 +53,20 @@ namespace Perspex.Styling
public Activator GetActivator(IStyleable control)
{
return new Activator(this, control);
List<IObservable<bool>> inputs = new List<IObservable<bool>>();
Selector selector = this;
while (selector != null)
{
if (selector.Observable != null)
{
inputs.Add(selector.Observable(control));
}
selector = selector.Previous;
}
return new Activator(inputs);
}
public override string ToString()

Loading…
Cancel
Save