From 768718e43f7b2cc228c4eb837bc7d79492f627e0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 25 May 2015 20:01:02 +0200 Subject: [PATCH] Started optimizing selectors. --- Perspex.Styling/IStyleable.cs | 13 +- Perspex.Styling/Perspex.Styling.csproj | 1 + Perspex.Styling/Selector.cs | 74 ++++----- Perspex.Styling/SelectorMatch.cs | 58 +++++++ Perspex.Styling/Selectors.cs | 154 ++++++++++-------- Perspex.Styling/Style.cs | 26 +-- .../Perspex.Styling.UnitTests.csproj | 2 +- .../SelectorTests_Class.cs | 21 +-- .../SelectorTests_Descendent.cs | 121 +++++++++----- .../SelectorTests_Multiple.cs | 2 +- .../SelectorTests_Name.cs | 47 +----- .../SelectorTests_OfType.cs | 46 +----- .../SelectorTests_Template.cs | 40 +++-- .../TestControlBase.cs | 7 +- .../TestSelectors.cs | 8 +- .../TestTemplatedControl.cs | 7 +- 16 files changed, 348 insertions(+), 279 deletions(-) create mode 100644 Perspex.Styling/SelectorMatch.cs diff --git a/Perspex.Styling/IStyleable.cs b/Perspex.Styling/IStyleable.cs index 7897606ade..8cb262c66a 100644 --- a/Perspex.Styling/IStyleable.cs +++ b/Perspex.Styling/IStyleable.cs @@ -43,9 +43,14 @@ namespace Perspex.Styling /// /// A disposable which can be used to terminate the binding. /// - IDisposable Bind( - PerspexProperty property, - IObservable source, - BindingPriority priority = BindingPriority.LocalValue); + IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority); + + /// + /// Sets a value. + /// + /// The property. + /// The value. + /// The priority of the value. + void SetValue(PerspexProperty property, object value, BindingPriority priority); } } diff --git a/Perspex.Styling/Perspex.Styling.csproj b/Perspex.Styling/Perspex.Styling.csproj index 0fb56010c4..5050b0bd04 100644 --- a/Perspex.Styling/Perspex.Styling.csproj +++ b/Perspex.Styling/Perspex.Styling.csproj @@ -44,6 +44,7 @@ + diff --git a/Perspex.Styling/Selector.cs b/Perspex.Styling/Selector.cs index 6e8523f199..2daccc63ae 100644 --- a/Perspex.Styling/Selector.cs +++ b/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 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 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(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> 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> inputs = new List>(); 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() diff --git a/Perspex.Styling/SelectorMatch.cs b/Perspex.Styling/SelectorMatch.cs new file mode 100644 index 0000000000..f3246ea4ef --- /dev/null +++ b/Perspex.Styling/SelectorMatch.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Styling +{ + using System; + + /// + /// Holds the result of a match. + /// + /// + /// There are two types of selectors - ones whose match can never change for a particular + /// control (such as ) and ones whose result can + /// change over time (such as . For the first + /// category of selectors, the value of will be set but for the + /// second, will be null and will + /// hold an observable which tracks the match. + /// + public class SelectorMatch + { + public static readonly SelectorMatch False = new SelectorMatch(false); + + public static readonly SelectorMatch True = new SelectorMatch(true); + + /// + /// Initializes a new instance of the class. + /// + /// The immediate match value. + public SelectorMatch(bool match) + { + this.ImmediateResult = match; + } + + /// + /// Initializes a new instance of the class. + /// + /// The observable match value. + public SelectorMatch(IObservable match) + { + this.ObservableResult = match; + } + + /// + /// Gets the immedate result of the selector match, in the case of selectors that cannot + /// change over time. + /// + public bool? ImmediateResult { get; } + + /// + /// Gets an observable which tracks the selector match, in the case of selectors that can + /// change over time. + /// + public IObservable ObservableResult { get; } + } +} diff --git a/Perspex.Styling/Selectors.cs b/Perspex.Styling/Selectors.cs index 65cf3cfee3..ec0f1e51ec 100644 --- a/Perspex.Styling/Selectors.cs +++ b/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(previous != null); Contract.Requires(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> descendentMatches = new List>(); - - while (c != null) - { - c = c.LogicalParent; - - if (c is IStyleable) - { - descendentMatches.Add(previous.GetActivator((IStyleable)c)); - } - } + Contract.Requires(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(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(this Selector previous) where T : IStyleable + public static Selector Is(this Selector previous) where T : IStyleable { - Contract.Requires(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(this Selector previous) where T : IStyleable + public static Selector Name(this Selector previous, string name) { Contract.Requires(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(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(this Selector previous) where T : IStyleable + { + return previous.OfType(typeof(T)); } public static Selector Template(this Selector previous) { Contract.Requires(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> descendentMatches = new List>(); + + 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); } } } diff --git a/Perspex.Styling/Style.cs b/Perspex.Styling/Style.cs index db36f92a51..74447338e7 100644 --- a/Perspex.Styling/Style.cs +++ b/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 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().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); } } } diff --git a/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index b02801da2c..e9b57a6931 100644 --- a/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/Tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -84,7 +84,7 @@ - + diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs index 0cf5c4f752..aad7792ec0 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_Class.cs +++ b/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"); diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs index 1dad4fb9be..c31ec40199 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs +++ b/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(); - var child = new Mock(); - var childStyleable = child.As(); + 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().Descendent().OfType(); - 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(); - var parent = new Mock(); - var child = new Mock(); + 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().Descendent().OfType(); - 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(); - var parent = new Mock(); - var child = new Mock(); + 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().Class("foo").Descendent().OfType(); + 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(); - var parent = new Mock(); - var child = new Mock(); + 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().Class("foo").Descendent().OfType(); + 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().Class("foo").Descendent().OfType(); - 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 LogicalChildren { get; } - public abstract ILogical LogicalParent { get; } + public TestLogical() + { + this.Classes = new Classes(); + } + + public Classes Classes { get; } + public string Name { get; set; } + public IPerspexReadOnlyList LogicalChildren { get; set; } + public ILogical LogicalParent { get; set; } public Type StyleKey { get; } - public abstract ITemplatedControl TemplatedParent { get; } - public abstract IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue); + public ITemplatedControl TemplatedParent { get; } + + public IDisposable Bind(PerspexProperty property, IObservable 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 { } } diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs index 373994b36e..fc27ae6421 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_Multiple.cs +++ b/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(); - var activator = selector.GetActivator(border); + var activator = selector.Match(border).ObservableResult; activator.Subscribe(x => values.Add(x)); diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_Name.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_Name.cs index 5a79307b3e..e7921863b6 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_Name.cs +++ b/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().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 diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_OfType.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_OfType.cs index 733c4c3391..1940556c48 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_OfType.cs +++ b/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(); - 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(); - 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(); - 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().Object }; var target = new Selector().OfType(); - 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().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().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 diff --git a/Tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs b/Tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs index 74efa48e5c..e83a538265 100644 --- a/Tests/Perspex.Styling.UnitTests/SelectorTests_Template.cs +++ b/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(); var templatedControl = target.As(); + var styleable = target.As(); this.BuildVisualTree(target); var border = (Border)target.Object.GetVisualChildren().Single(); + var selector = new Selector().Template().OfType(); + + Assert.True(selector.Match(border).ImmediateResult); + } + + [Fact] + public void Control_Not_In_Template_Is_Not_Matched_With_Template_Selector() + { + var target = new Mock(); + var templatedControl = target.As(); + var styleable = target.As(); + this.BuildVisualTree(target); + var border = (Border)target.Object.GetVisualChildren().Single(); + border.SetValue(Border.TemplatedParentProperty, null); var selector = new Selector().Template().OfType(); - 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(); var templatedControl = target.As(); + var styleable = target.As(); this.BuildVisualTree(target); var textBlock = (TextBlock)target.Object.VisualChildren.Single().VisualChildren.Single(); var selector = new Selector().Template().OfType(); - 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(); var templatedControl = target.As(); + var styleable = target.As(); + 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(); - var activator = selector.GetActivator(border); + var selector = new Selector().OfType(styleKey).Template().OfType(); - 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(); var styleable = target.As(); 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(); - 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(); - var activator = selector.GetActivator(border); + var activator = selector.Match(border).ObservableResult; Assert.False(await activator.Take(1)); } diff --git a/Tests/Perspex.Styling.UnitTests/TestControlBase.cs b/Tests/Perspex.Styling.UnitTests/TestControlBase.cs index 6fa32919b3..b8681a3d2e 100644 --- a/Tests/Perspex.Styling.UnitTests/TestControlBase.cs +++ b/Tests/Perspex.Styling.UnitTests/TestControlBase.cs @@ -34,7 +34,12 @@ namespace Perspex.Styling.UnitTests set; } - public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) + { + throw new NotImplementedException(); + } + + public void SetValue(PerspexProperty property, object value, BindingPriority priority) { throw new NotImplementedException(); } diff --git a/Tests/Perspex.Styling.UnitTests/TestSelectors.cs b/Tests/Perspex.Styling.UnitTests/TestSelectors.cs index 56e7ffc655..44542609c3 100644 --- a/Tests/Perspex.Styling.UnitTests/TestSelectors.cs +++ b/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), + ""); } } } diff --git a/Tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs b/Tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs index 0549dbd97b..24d705edc5 100644 --- a/Tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs +++ b/Tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs @@ -42,7 +42,12 @@ namespace Perspex.Styling.UnitTests throw new NotImplementedException(); } - public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) + public IDisposable Bind(PerspexProperty property, IObservable source, BindingPriority priority) + { + throw new NotImplementedException(); + } + + public void SetValue(PerspexProperty property, object value, BindingPriority priority) { throw new NotImplementedException(); }