Browse Source

Started optimizing selectors.

pull/58/head
Steven Kirk 11 years ago
parent
commit
768718e43f
  1. 13
      Perspex.Styling/IStyleable.cs
  2. 1
      Perspex.Styling/Perspex.Styling.csproj
  3. 74
      Perspex.Styling/Selector.cs
  4. 58
      Perspex.Styling/SelectorMatch.cs
  5. 154
      Perspex.Styling/Selectors.cs
  6. 26
      Perspex.Styling/Style.cs
  7. 2
      Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj
  8. 21
      Tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs
  9. 121
      Tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
  10. 2
      Tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs
  11. 47
      Tests/Perspex.Styling.UnitTests/SelectorTests_Name.cs
  12. 46
      Tests/Perspex.Styling.UnitTests/SelectorTests_OfType.cs
  13. 40
      Tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs
  14. 7
      Tests/Perspex.Styling.UnitTests/TestControlBase.cs
  15. 8
      Tests/Perspex.Styling.UnitTests/TestSelectors.cs
  16. 7
      Tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

13
Perspex.Styling/IStyleable.cs

@ -43,9 +43,14 @@ namespace Perspex.Styling
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind(
PerspexProperty property,
IObservable<object> source,
BindingPriority priority = BindingPriority.LocalValue);
IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority);
/// <summary>
/// Sets a <see cref="PerspexProperty"/> value.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
void SetValue(PerspexProperty property, object value, BindingPriority priority);
}
}

1
Perspex.Styling/Perspex.Styling.csproj

@ -44,6 +44,7 @@
<Compile Include="IStyleHost.cs" />
<Compile Include="IStyler.cs" />
<Compile Include="ITemplatedControl.cs" />
<Compile Include="SelectorMatch.cs" />
<Compile Include="Selector.cs" />
<Compile Include="Selectors.cs" />
<Compile Include="Setter.cs" />

74
Perspex.Styling/Selector.cs

@ -8,45 +8,35 @@ namespace Perspex.Styling
{
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
public class Selector
{
private Func<IStyleable, SelectorMatch> evaluate;
private bool inTemplate;
private bool stopTraversal;
public Selector()
{
this.GetObservable = _ => Observable.Return(true);
this.Priority = BindingPriority.Style;
this.evaluate = _ => new SelectorMatch(true);
}
public Selector(Selector previous, bool stopTraversal = false)
public Selector(
Selector previous,
Func<IStyleable, SelectorMatch> evaluate,
string selectorString,
bool inTemplate = false,
bool stopTraversal = false)
: this()
{
this.Previous = previous;
this.Priority = previous.Priority;
this.InTemplate = previous != null ? previous.InTemplate : false;
this.stopTraversal = stopTraversal;
}
Contract.Requires<ArgumentNullException>(previous != null);
public Selector(Selector previous, BindingPriority priority)
: this()
{
this.Previous = previous;
this.Priority = priority;
this.InTemplate = previous != null ? previous.InTemplate : false;
}
public bool InTemplate
{
get;
set;
}
public Func<IStyleable, IObservable<bool>> GetObservable
{
get;
set;
this.evaluate = evaluate;
this.SelectorString = selectorString;
this.inTemplate = inTemplate || previous.inTemplate;
this.stopTraversal = stopTraversal;
}
public Selector Previous
@ -55,12 +45,6 @@ namespace Perspex.Styling
private set;
}
public BindingPriority Priority
{
get;
private set;
}
public string SelectorString
{
get;
@ -72,26 +56,40 @@ namespace Perspex.Styling
return this.stopTraversal ? null : this.Previous;
}
public StyleActivator GetActivator(IStyleable control)
public SelectorMatch Match(IStyleable control)
{
List<IObservable<bool>> inputs = new List<IObservable<bool>>();
Selector selector = this;
while (selector != null)
{
if (selector.InTemplate && control.TemplatedParent == null)
if (selector.inTemplate && control.TemplatedParent == null)
{
inputs.Add(Observable.Return(false));
return SelectorMatch.False;
}
else
var match = selector.evaluate(control);
if (match.ImmediateResult == false)
{
return match;
}
else if (match.ObservableResult != null)
{
inputs.Add(selector.GetObservable(control));
inputs.Add(match.ObservableResult);
}
selector = selector.MovePrevious();
}
return new StyleActivator(inputs);
if (inputs.Count > 0)
{
return new SelectorMatch(new StyleActivator(inputs));
}
else
{
return SelectorMatch.True;
}
}
public override string ToString()

58
Perspex.Styling/SelectorMatch.cs

@ -0,0 +1,58 @@
// -----------------------------------------------------------------------
// <copyright file="SelectorMatch.cs" company="Steven Kirk">
// Copyright 2015 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Styling
{
using System;
/// <summary>
/// Holds the result of a <see cref="Selector"/> match.
/// </summary>
/// <remarks>
/// There are two types of selectors - ones whose match can never change for a particular
/// control (such as <see cref="Selectors.OfType{T}(Selector)"/>) and ones whose result can
/// change over time (such as <see cref="Selectors.Class(Selector, string)"/>. For the first
/// category of selectors, the value of <see cref="ImmediateResult"/> will be set but for the
/// second, <see cref="ImmediateResult"/> will be null and <see cref="ObservableResult"/> will
/// hold an observable which tracks the match.
/// </remarks>
public class SelectorMatch
{
public static readonly SelectorMatch False = new SelectorMatch(false);
public static readonly SelectorMatch True = new SelectorMatch(true);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// </summary>
/// <param name="match">The immediate match value.</param>
public SelectorMatch(bool match)
{
this.ImmediateResult = match;
}
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// </summary>
/// <param name="match">The observable match value.</param>
public SelectorMatch(IObservable<bool> match)
{
this.ObservableResult = match;
}
/// <summary>
/// Gets the immedate result of the selector match, in the case of selectors that cannot
/// change over time.
/// </summary>
public bool? ImmediateResult { get; }
/// <summary>
/// Gets an observable which tracks the selector match, in the case of selectors that can
/// change over time.
/// </summary>
public IObservable<bool> ObservableResult { get; }
}
}

154
Perspex.Styling/Selectors.cs

@ -9,6 +9,7 @@ namespace Perspex.Styling
using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reflection;
public static class Selectors
{
@ -17,102 +18,127 @@ namespace Perspex.Styling
Contract.Requires<ArgumentNullException>(previous != null);
Contract.Requires<ArgumentNullException>(name != null);
return new Selector(previous, BindingPriority.StyleTrigger)
{
GetObservable = control => Observable
.Return(control.Classes.Contains(name))
.Concat(control.Classes.Changed.Select(e => control.Classes.Contains(name))),
SelectorString = (name[0] == ':') ? name : '.' + name,
};
return new Selector(previous, x => MatchClass(x, name), name);
}
public static Selector Descendent(this Selector previous)
{
return new Selector(previous, stopTraversal: true)
{
SelectorString = " ",
GetObservable = control =>
{
ILogical c = (ILogical)control;
List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>();
while (c != null)
{
c = c.LogicalParent;
if (c is IStyleable)
{
descendentMatches.Add(previous.GetActivator((IStyleable)c));
}
}
Contract.Requires<ArgumentNullException>(previous != null);
return new StyleActivator(
descendentMatches,
ActivatorMode.Or);
},
};
return new Selector(previous, x => MatchDescendent(x, previous), " ", stopTraversal: true);
}
public static Selector Name(this Selector previous, string id)
public static Selector Is(this Selector previous, Type type)
{
Contract.Requires<ArgumentNullException>(previous != null);
return new Selector(previous)
{
GetObservable = control => Observable.Return(control.Name == id),
SelectorString = '#' + id,
};
return new Selector(previous, x => MatchIs(x, type), type.Name);
}
public static Selector OfType<T>(this Selector previous) where T : IStyleable
public static Selector Is<T>(this Selector previous) where T : IStyleable
{
Contract.Requires<ArgumentNullException>(previous != null);
return new Selector(previous)
{
GetObservable = control => Observable.Return(control.StyleKey == typeof(T)),
SelectorString = typeof(T).Name,
};
return previous.Is(typeof(T));
}
public static Selector Is<T>(this Selector previous) where T : IStyleable
public static Selector Name(this Selector previous, string name)
{
Contract.Requires<ArgumentNullException>(previous != null);
return new Selector(previous)
{
GetObservable = control => Observable.Return(control is T),
SelectorString = ":is(" + typeof(T).Name + ")",
};
return new Selector(previous, x => MatchName(x, name), '#' + name);
}
public static Selector OfType(this Selector previous, Type type)
{
Contract.Requires<ArgumentNullException>(previous != null);
return new Selector(previous)
{
GetObservable = control => Observable.Return(control.GetType() == type),
SelectorString = type.Name,
};
return new Selector(previous, x => MatchOfType(x, type), type.Name);
}
public static Selector OfType<T>(this Selector previous) where T : IStyleable
{
return previous.OfType(typeof(T));
}
public static Selector Template(this Selector previous)
{
Contract.Requires<ArgumentNullException>(previous != null);
return new Selector(previous, stopTraversal: true)
return new Selector(
previous,
x => MatchTemplate(x, previous),
" /deep/ ",
inTemplate: true,
stopTraversal: true);
}
private static SelectorMatch MatchClass(IStyleable control, string name)
{
return new SelectorMatch(
Observable
.Return(control.Classes.Contains(name))
.Concat(control.Classes.Changed.Select(e => control.Classes.Contains(name))));
}
private static SelectorMatch MatchDescendent(IStyleable control, Selector previous)
{
ILogical c = (ILogical)control;
List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>();
while (c != null)
{
GetObservable = control =>
c = c.LogicalParent;
if (c is IStyleable)
{
IStyleable templatedParent = control.TemplatedParent as IStyleable;
return templatedParent != null ?
previous.GetActivator(templatedParent) :
Observable.Return(true);
},
InTemplate = true,
SelectorString = " $ ",
};
var match = previous.Match((IStyleable)c);
if (match.ImmediateResult != null)
{
if (match.ImmediateResult == true)
{
return new SelectorMatch(true);
}
}
else
{
descendentMatches.Add(match.ObservableResult);
}
}
}
return new SelectorMatch(new StyleActivator(
descendentMatches,
ActivatorMode.Or));
}
private static SelectorMatch MatchIs(IStyleable control, Type type)
{
var controlType = control.StyleKey ?? control.GetType();
return new SelectorMatch(type.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo()));
}
private static SelectorMatch MatchName(IStyleable control, string name)
{
return new SelectorMatch(control.Name == name);
}
private static SelectorMatch MatchOfType(IStyleable control, Type type)
{
var controlType = control.StyleKey ?? control.GetType();
return new SelectorMatch(controlType == type);
}
private static SelectorMatch MatchTemplate(IStyleable control, Selector previous)
{
IStyleable templatedParent = control.TemplatedParent as IStyleable;
if (templatedParent == null)
{
throw new InvalidOperationException(
"Cannot call Template selector on control with null TemplatedParent.");
}
return previous.Match(templatedParent);
}
}
}

26
Perspex.Styling/Style.cs

@ -37,25 +37,25 @@ namespace Perspex.Styling
public void Attach(IStyleable control)
{
string description = "Style " + this.Selector.ToString();
StyleActivator activator = this.Selector.GetActivator(control);
var description = "Style " + this.Selector.ToString();
var match = this.Selector.Match(control);
if (activator.CurrentValue || !activator.HasCompleted)
if (match.ImmediateResult.HasValue)
{
IObservable<bool> observable = activator;
// If the activator has completed, then we want its value to be true forever.
// Because of this we can't pass the activator directly as it will complete
// immediately and remove the binding.
if (activator.HasCompleted)
if (match.ImmediateResult == true)
{
observable = Observable.Never<bool>().StartWith(true);
foreach (Setter setter in this.Setters)
{
control.SetValue(setter.Property, setter.Value, BindingPriority.Style);
}
}
}
else
{
foreach (Setter setter in this.Setters)
{
StyleBinding binding = new StyleBinding(observable, setter.Value, description);
control.Bind(setter.Property, binding, this.Selector.Priority);
var binding = new StyleBinding(match.ObservableResult, setter.Value, description);
control.Bind(setter.Property, binding, BindingPriority.StyleTrigger);
}
}
}

2
Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj

@ -84,7 +84,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SelectorTests_Class.cs" />
<Compile Include="SelectorTests_Descendent.cs" />
<Compile Include="SelectorTests_Id.cs" />
<Compile Include="SelectorTests_Name.cs" />
<Compile Include="SelectorTests_Multiple.cs" />
<Compile Include="SelectorTests_OfType.cs" />
<Compile Include="SelectorTests_Template.cs" />

21
Tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs

@ -15,15 +15,6 @@ namespace Perspex.Styling.UnitTests
public class SelectorTests_Class
{
[Fact]
public void Class_Priority_Is_StyleTrigger()
{
var control = new Control1();
var target = new Selector().Class("foo");
Assert.Equal(BindingPriority.StyleTrigger, target.Priority);
}
[Fact]
public async Task Class_Matches_Control_With_Class()
{
@ -33,7 +24,7 @@ namespace Perspex.Styling.UnitTests
};
var target = new Selector().Class("foo");
var activator = target.GetActivator(control);
var activator = target.Match(control).ObservableResult;
Assert.True(await activator.Take(1));
}
@ -47,7 +38,7 @@ namespace Perspex.Styling.UnitTests
};
var target = new Selector().Class("foo");
var activator = target.GetActivator(control);
var activator = target.Match(control).ObservableResult;
Assert.False(await activator.Take(1));
}
@ -62,7 +53,7 @@ namespace Perspex.Styling.UnitTests
};
var target = new Selector().Class("foo");
var activator = target.GetActivator(control);
var activator = target.Match(control).ObservableResult;
Assert.True(await activator.Take(1));
}
@ -73,7 +64,7 @@ namespace Perspex.Styling.UnitTests
var control = new Control1();
var target = new Selector().Class("foo");
var activator = target.GetActivator(control);
var activator = target.Match(control).ObservableResult;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@ -89,7 +80,7 @@ namespace Perspex.Styling.UnitTests
};
var target = new Selector().Class("foo");
var activator = target.GetActivator(control);
var activator = target.Match(control).ObservableResult;
Assert.True(await activator.Take(1));
control.Classes.Remove("foo");
@ -101,7 +92,7 @@ namespace Perspex.Styling.UnitTests
{
var control = new Control1();
var target = new Selector().Class("foo").Class("bar");
var activator = target.GetActivator(control);
var activator = target.Match(control).ObservableResult;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");

121
Tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs

@ -10,7 +10,6 @@ namespace Perspex.Styling.UnitTests
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Moq;
using Perspex.Collections;
using Perspex.Styling;
using Xunit;
@ -18,50 +17,46 @@ namespace Perspex.Styling.UnitTests
public class SelectorTests_Descendent
{
[Fact]
public async Task Descendent_Matches_Control_When_It_Is_Child_OfType()
public void Descendent_Matches_Control_When_It_Is_Child_OfType()
{
var parent = new Mock<TestLogical1>();
var child = new Mock<TestLogical2>();
var childStyleable = child.As<IStyleable>();
var parent = new TestLogical1();
var child = new TestLogical2();
child.Setup(x => x.LogicalParent).Returns(parent.Object);
child.LogicalParent = parent;
var selector = new Selector().OfType(parent.Object.GetType()).Descendent().OfType(child.Object.GetType());
var activator = selector.GetActivator(childStyleable.Object);
var selector = new Selector().OfType<TestLogical1>().Descendent().OfType<TestLogical2>();
Assert.True(await activator.Take(1));
Assert.True(selector.Match(child).ImmediateResult);
}
[Fact]
public async Task Descendent_Matches_Control_When_It_Is_Descendent_OfType()
public void Descendent_Matches_Control_When_It_Is_Descendent_OfType()
{
var grandparent = new Mock<TestLogical1>();
var parent = new Mock<TestLogical2>();
var child = new Mock<TestLogical3>();
var grandparent = new TestLogical1();
var parent = new TestLogical2();
var child = new TestLogical3();
parent.Setup(x => x.LogicalParent).Returns(grandparent.Object);
child.Setup(x => x.LogicalParent).Returns(parent.Object);
parent.LogicalParent = grandparent;
child.LogicalParent = parent;
var selector = new Selector().OfType(grandparent.Object.GetType()).Descendent().OfType(child.Object.GetType());
var activator = selector.GetActivator(child.Object);
var selector = new Selector().OfType<TestLogical1>().Descendent().OfType<TestLogical3>();
Assert.True(await activator.Take(1));
Assert.True(selector.Match(child).ImmediateResult);
}
[Fact]
public async Task Descendent_Matches_Control_When_It_Is_Descendent_OfType_And_Class()
{
var grandparent = new Mock<TestLogical1>();
var parent = new Mock<TestLogical2>();
var child = new Mock<TestLogical3>();
var grandparent = new TestLogical1();
var parent = new TestLogical2();
var child = new TestLogical3();
grandparent.Setup(x => x.Classes).Returns(new Classes("foo"));
parent.Setup(x => x.LogicalParent).Returns(grandparent.Object);
parent.Setup(x => x.Classes).Returns(new Classes());
child.Setup(x => x.LogicalParent).Returns(parent.Object);
grandparent.Classes.Add("foo");
parent.LogicalParent = grandparent;
child.LogicalParent = parent;
var selector = new Selector().OfType(grandparent.Object.GetType()).Class("foo").Descendent().OfType(child.Object.GetType());
var activator = selector.GetActivator(child.Object);
var selector = new Selector().OfType<TestLogical1>().Class("foo").Descendent().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
Assert.True(await activator.Take(1));
}
@ -69,41 +64,79 @@ namespace Perspex.Styling.UnitTests
[Fact]
public async Task Descendent_Doesnt_Match_Control_When_It_Is_Descendent_OfType_But_Wrong_Class()
{
var grandparent = new Mock<TestLogical1>();
var parent = new Mock<TestLogical2>();
var child = new Mock<TestLogical3>();
var grandparent = new TestLogical1();
var parent = new TestLogical2();
var child = new TestLogical3();
grandparent.Classes.Add("bar");
parent.LogicalParent = grandparent;
parent.Classes.Add("foo");
child.LogicalParent = parent;
var selector = new Selector().OfType<TestLogical1>().Class("foo").Descendent().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
Assert.False(await activator.Take(1));
}
[Fact]
public async Task Descendent_Matches_Any_Ancestor()
{
var grandparent = new TestLogical1();
var parent = new TestLogical1();
var child = new TestLogical3();
grandparent.Setup(x => x.Classes).Returns(new Classes("bar"));
parent.Setup(x => x.LogicalParent).Returns(grandparent.Object);
parent.Setup(x => x.Classes).Returns(new Classes("foo"));
child.Setup(x => x.LogicalParent).Returns(parent.Object);
parent.LogicalParent = grandparent;
child.LogicalParent = parent;
var selector = new Selector().OfType<TestLogical1>().Class("foo").Descendent().OfType<TestLogical3>();
var activator = selector.GetActivator(child.Object);
var activator = selector.Match(child).ObservableResult;
Assert.False(await activator.Take(1));
parent.Classes.Add("foo");
Assert.True(await activator.Take(1));
grandparent.Classes.Add("foo");
Assert.True(await activator.Take(1));
parent.Classes.Remove("foo");
Assert.True(await activator.Take(1));
grandparent.Classes.Remove("foo");
Assert.False(await activator.Take(1));
}
public abstract class TestLogical : ILogical, IStyleable
{
public abstract Classes Classes { get; }
public abstract string Name { get; }
public abstract IPerspexReadOnlyList<ILogical> LogicalChildren { get; }
public abstract ILogical LogicalParent { get; }
public TestLogical()
{
this.Classes = new Classes();
}
public Classes Classes { get; }
public string Name { get; set; }
public IPerspexReadOnlyList<ILogical> LogicalChildren { get; set; }
public ILogical LogicalParent { get; set; }
public Type StyleKey { get; }
public abstract ITemplatedControl TemplatedParent { get; }
public abstract IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue);
public ITemplatedControl TemplatedParent { get; }
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}
}
public abstract class TestLogical1 : TestLogical
public class TestLogical1 : TestLogical
{
}
public abstract class TestLogical2 : TestLogical
public class TestLogical2 : TestLogical
{
}
public abstract class TestLogical3 : TestLogical
public class TestLogical3 : TestLogical
{
}
}

2
Tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs

@ -42,7 +42,7 @@ namespace Perspex.Styling.UnitTests
var border = (Border)((IVisual)control).VisualChildren.Single();
var values = new List<bool>();
var activator = selector.GetActivator(border);
var activator = selector.Match(border).ObservableResult;
activator.Subscribe(x => values.Add(x));

47
Tests/Perspex.Styling.UnitTests/SelectorTests_Name.cs

@ -16,64 +16,31 @@ namespace Perspex.Styling.UnitTests
public class SelectorTests_Name
{
[Fact]
public void Name_Priority_Is_Style()
{
var control = new Control1();
var target = new Selector().Name("foo");
Assert.Equal(BindingPriority.Style, target.Priority);
}
[Fact]
public async Task Name_Matches_Control_With_Correct_Name()
public void Name_Matches_Control_With_Correct_Name()
{
var control = new Control1 { Name = "foo" };
var target = new Selector().Name("foo");
var activator = target.GetActivator(control);
Assert.True(await activator.Take(1));
Assert.True(target.Match(control).ImmediateResult);
}
[Fact]
public async Task Name_Doesnt_Match_Control_Of_Wrong_Name()
public void Name_Doesnt_Match_Control_Of_Wrong_Name()
{
var control = new Control1 { Name = "foo" };
var target = new Selector().Name("bar");
var activator = target.GetActivator(control);
Assert.False(await activator.Take(1));
Assert.False(target.Match(control).ImmediateResult);
}
[Fact]
public async Task Name_Doesnt_Match_Control_With_TemplatedParent()
public void Name_Doesnt_Match_Control_With_TemplatedParent()
{
var control = new Control1 { TemplatedParent = new Mock<ITemplatedControl>().Object };
var target = new Selector().Name("foo");
var activator = target.GetActivator(control);
Assert.False(await activator.Take(1));
}
[Fact]
public async Task When_Name_Matches_Control_Other_Selectors_Are_Subscribed()
{
var control = new Control1 { Name = "foo" };
var target = new Selector().Name("foo").SubscribeCheck();
var result = await target.GetActivator(control).Take(1);
Assert.Equal(1, control.SubscribeCheckObservable.SubscribedCount);
}
[Fact]
public async Task When_Name_Doesnt_Match_Control_Other_Selectors_Are_Not_Subscribed()
{
var control = new Control1 { Name = "foo" };
var target = new Selector().Name("bar").SubscribeCheck();
var result = await target.GetActivator(control).Take(1);
var activator = target.Match(control);
Assert.Equal(0, control.SubscribeCheckObservable.SubscribedCount);
Assert.False(target.Match(control).ImmediateResult);
}
public class Control1 : TestControlBase

46
Tests/Perspex.Styling.UnitTests/SelectorTests_OfType.cs

@ -16,64 +16,30 @@ namespace Perspex.Styling.UnitTests
public class SelectorTests_OfType
{
[Fact]
public void OfType_Priority_Is_Style()
public void OfType_Matches_Control_Of_Correct_Type()
{
var control = new Control1();
var target = new Selector().OfType<Control1>();
Assert.Equal(BindingPriority.Style, target.Priority);
Assert.True(target.Match(control).ImmediateResult);
}
[Fact]
public async Task OfType_Matches_Control_Of_Correct_Type()
{
var control = new Control1();
var target = new Selector().OfType<Control1>();
var activator = target.GetActivator(control);
Assert.True(await activator.Take(1));
}
[Fact]
public async Task OfType_Doesnt_Match_Control_Of_Wrong_Type()
public void OfType_Doesnt_Match_Control_Of_Wrong_Type()
{
var control = new Control2();
var target = new Selector().OfType<Control1>();
var activator = target.GetActivator(control);
Assert.False(await activator.Take(1));
Assert.False(target.Match(control).ImmediateResult);
}
[Fact]
public async Task OfType_Matches_Control_With_TemplatedParent()
public void OfType_Matches_Control_With_TemplatedParent()
{
var control = new Control1 { TemplatedParent = new Mock<ITemplatedControl>().Object };
var target = new Selector().OfType<Control1>();
var activator = target.GetActivator(control);
Assert.True(await activator.Take(1));
}
[Fact]
public async Task When_OfType_Matches_Control_Other_Selectors_Are_Subscribed()
{
var control = new Control1();
var target = new Selector().OfType<Control1>().SubscribeCheck();
var result = await target.GetActivator(control).Take(1);
Assert.Equal(1, control.SubscribeCheckObservable.SubscribedCount);
}
[Fact]
public async Task When_OfType_Doesnt_Match_Control_Other_Selectors_Are_Not_Subscribed()
{
var control = new Control1();
var target = new Selector().OfType<Control2>().SubscribeCheck();
var result = await target.GetActivator(control).Take(1);
Assert.Equal(0, control.SubscribeCheckObservable.SubscribedCount);
Assert.True(target.Match(control).ImmediateResult);
}
public class Control1 : TestControlBase

40
Tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs

@ -18,47 +18,62 @@ namespace Perspex.Styling.UnitTests
public class SelectorTests_Template
{
[Fact]
public async Task Control_In_Template_Is_Matched_With_Template_Selector()
public void Control_In_Template_Is_Matched_With_Template_Selector()
{
var target = new Mock<IVisual>();
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
this.BuildVisualTree(target);
var border = (Border)target.Object.GetVisualChildren().Single();
var selector = new Selector().Template().OfType<Border>();
Assert.True(selector.Match(border).ImmediateResult);
}
[Fact]
public void Control_Not_In_Template_Is_Not_Matched_With_Template_Selector()
{
var target = new Mock<IVisual>();
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
this.BuildVisualTree(target);
var border = (Border)target.Object.GetVisualChildren().Single();
border.SetValue(Border.TemplatedParentProperty, null);
var selector = new Selector().Template().OfType<Border>();
var activator = selector.GetActivator(border);
Assert.True(await activator.Take(1));
Assert.False(selector.Match(border).ImmediateResult);
}
[Fact]
public async Task Nested_Control_In_Template_Is_Matched_With_Template_Selector()
public void Nested_Control_In_Template_Is_Matched_With_Template_Selector()
{
var target = new Mock<IVisual>();
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
this.BuildVisualTree(target);
var textBlock = (TextBlock)target.Object.VisualChildren.Single().VisualChildren.Single();
var selector = new Selector().Template().OfType<TextBlock>();
var activator = selector.GetActivator(textBlock);
Assert.True(await activator.Take(1));
Assert.True(selector.Match(textBlock).ImmediateResult);
}
[Fact]
public async Task Control_In_Template_Is_Matched_With_TypeOf_TemplatedControl()
public void Control_In_Template_Is_Matched_With_TypeOf_TemplatedControl()
{
var target = new Mock<IVisual>();
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
var styleKey = templatedControl.Object.GetType();
this.BuildVisualTree(target);
var border = (Border)target.Object.VisualChildren.Single();
var selector = new Selector().OfType(templatedControl.Object.GetType()).Template().OfType<Border>();
var activator = selector.GetActivator(border);
var selector = new Selector().OfType(styleKey).Template().OfType<Border>();
Assert.True(await activator.Take(1));
Assert.True(selector.Match(border).ImmediateResult);
}
[Fact]
@ -68,14 +83,13 @@ namespace Perspex.Styling.UnitTests
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
var styleKey = templatedControl.Object.GetType();
this.BuildVisualTree(target);
styleable.Setup(x => x.StyleKey).Returns(styleKey);
styleable.Setup(x => x.Classes).Returns(new Classes("foo"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = new Selector().OfType(styleKey).Class("foo").Template().OfType<Border>();
var activator = selector.GetActivator(border);
var activator = selector.Match(border).ObservableResult;
Assert.True(await activator.Take(1));
}
@ -91,7 +105,7 @@ namespace Perspex.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("bar"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = new Selector().OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType<Border>();
var activator = selector.GetActivator(border);
var activator = selector.Match(border).ObservableResult;
Assert.False(await activator.Take(1));
}

7
Tests/Perspex.Styling.UnitTests/TestControlBase.cs

@ -34,7 +34,12 @@ namespace Perspex.Styling.UnitTests
set;
}
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}

8
Tests/Perspex.Styling.UnitTests/TestSelectors.cs

@ -12,10 +12,10 @@ namespace Perspex.Styling.UnitTests
{
public static Selector SubscribeCheck(this Selector selector)
{
return new Selector(selector)
{
GetObservable = control => ((TestControlBase)control).SubscribeCheckObservable,
};
return new Selector(
selector,
control => new SelectorMatch(((TestControlBase)control).SubscribeCheckObservable),
"");
}
}
}

7
Tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

@ -42,7 +42,12 @@ namespace Perspex.Styling.UnitTests
throw new NotImplementedException();
}
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority = BindingPriority.LocalValue)
public IDisposable Bind(PerspexProperty property, IObservable<object> source, BindingPriority priority)
{
throw new NotImplementedException();
}
public void SetValue(PerspexProperty property, object value, BindingPriority priority)
{
throw new NotImplementedException();
}

Loading…
Cancel
Save