Browse Source

Merge pull request #2088 from AvaloniaUI/perf/style-type-cache

[Perf] Cache the possible styles for control types.
pull/2124/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
7b6a65c861
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      src/Avalonia.Styling/Styling/ChildSelector.cs
  2. 16
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  3. 7
      src/Avalonia.Styling/Styling/IStyle.cs
  4. 6
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  5. 39
      src/Avalonia.Styling/Styling/Selector.cs
  6. 86
      src/Avalonia.Styling/Styling/SelectorMatch.cs
  7. 24
      src/Avalonia.Styling/Styling/Style.cs
  8. 45
      src/Avalonia.Styling/Styling/Styles.cs
  9. 5
      src/Avalonia.Styling/Styling/TemplateSelector.cs
  10. 19
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  11. 6
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  12. 8
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  13. 23
      tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
  14. 10
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  15. 40
      tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
  16. 6
      tests/Avalonia.Styling.UnitTests/SelectorTests_Name.cs
  17. 15
      tests/Avalonia.Styling.UnitTests/SelectorTests_OfType.cs
  18. 11
      tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
  19. 31
      tests/Avalonia.Styling.UnitTests/SelectorTests_Template.cs

20
src/Avalonia.Styling/Styling/ChildSelector.cs

@ -24,6 +24,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override Type TargetType => null;
@ -43,11 +46,24 @@ namespace Avalonia.Styling
if (controlParent != null)
{
return _parent.Match((IStyleable)controlParent, subscribe);
var parentMatch = _parent.Match((IStyleable)controlParent, subscribe);
if (parentMatch.Result == SelectorMatchResult.Sometimes)
{
return parentMatch;
}
else if (parentMatch.IsMatch)
{
return SelectorMatch.AlwaysThisInstance;
}
else
{
return SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
}

16
src/Avalonia.Styling/Styling/DescendentSelector.cs

@ -22,6 +22,9 @@ namespace Avalonia.Styling
_parent = parent;
}
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
@ -51,16 +54,13 @@ namespace Avalonia.Styling
{
var match = _parent.Match((IStyleable)c, subscribe);
if (match.ImmediateResult != null)
if (match.Result == SelectorMatchResult.Sometimes)
{
if (match.ImmediateResult == true)
{
return SelectorMatch.True;
}
descendantMatches.Add(match.Activator);
}
else
else if (match.IsMatch)
{
descendantMatches.Add(match.ObservableResult);
return SelectorMatch.AlwaysThisInstance;
}
}
}
@ -71,7 +71,7 @@ namespace Avalonia.Styling
}
else
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
}

7
src/Avalonia.Styling/Styling/IStyle.cs

@ -17,7 +17,12 @@ namespace Avalonia.Styling
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
void Attach(IStyleable control, IStyleHost container);
/// <returns>
/// True if the style can match a control of type <paramref name="control"/>
/// (even if it does not match this control specifically); false if the style
/// can never match.
/// </returns>
bool Attach(IStyleable control, IStyleHost container);
void Detach();
}

6
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@ -30,6 +30,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <summary>
/// Gets the name of the control to match.
/// </summary>
@ -78,7 +81,8 @@ namespace Avalonia.Styling
}
else
{
return new SelectorMatch((control.GetValue(_property) ?? string.Empty).Equals(_value));
var result = (control.GetValue(_property) ?? string.Empty).Equals(_value);
return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
}
}

39
src/Avalonia.Styling/Styling/Selector.cs

@ -17,6 +17,14 @@ namespace Avalonia.Styling
/// </summary>
public abstract bool InTemplate { get; }
/// <summary>
/// Gets a value indicating whether this selector is a combinator.
/// </summary>
/// <remarks>
/// A combinator is a selector such as Child or Descendent which links simple selectors.
/// </remarks>
public abstract bool IsCombinator { get; }
/// <summary>
/// Gets the target type of the selector, if available.
/// </summary>
@ -33,25 +41,32 @@ namespace Avalonia.Styling
/// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(IStyleable control, bool subscribe = true)
{
List<IObservable<bool>> inputs = new List<IObservable<bool>>();
Selector selector = this;
var inputs = new List<IObservable<bool>>();
var selector = this;
var alwaysThisType = true;
var hitCombinator = false;
while (selector != null)
{
if (selector.InTemplate && control.TemplatedParent == null)
{
return SelectorMatch.False;
}
hitCombinator |= selector.IsCombinator;
var match = selector.Evaluate(control, subscribe);
if (match.ImmediateResult == false)
if (!match.IsMatch)
{
return hitCombinator ? SelectorMatch.NeverThisInstance : match;
}
else if (selector.InTemplate && control.TemplatedParent == null)
{
return SelectorMatch.NeverThisInstance;
}
else if (match.Result == SelectorMatchResult.AlwaysThisInstance)
{
return match;
alwaysThisType = false;
}
else if (match.ObservableResult != null)
else if (match.Result == SelectorMatchResult.Sometimes)
{
inputs.Add(match.ObservableResult);
inputs.Add(match.Activator);
}
selector = selector.MovePrevious();
@ -63,7 +78,9 @@ namespace Avalonia.Styling
}
else
{
return SelectorMatch.True;
return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance;
}
}

86
src/Avalonia.Styling/Styling/SelectorMatch.cs

@ -5,51 +5,95 @@ using System;
namespace Avalonia.Styling
{
/// <summary>
/// Describes how a <see cref="SelectorMatch"/> matches a control and its type.
/// </summary>
public enum SelectorMatchResult
{
/// <summary>
/// The selector never matches this type.
/// </summary>
NeverThisType,
/// <summary>
/// The selector never matches this instance, but can match this type.
/// </summary>
NeverThisInstance,
/// <summary>
/// The selector always matches this type.
/// </summary>
AlwaysThisType,
/// <summary>
/// The selector always matches this instance, but doesn't always match this type.
/// </summary>
AlwaysThisInstance,
/// <summary>
/// The selector matches this instance based on the <see cref="SelectorMatch.Activator"/>.
/// </summary>
Sometimes,
}
/// <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(Selector, Type)"/>) 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.
/// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
/// in addition whether the selector can ever match a control of the same type.
/// </remarks>
public class SelectorMatch
{
public static readonly SelectorMatch False = new SelectorMatch(false);
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.
/// </summary>
public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType);
public static readonly SelectorMatch True = new SelectorMatch(true);
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisInstance"/>.
/// </summary>
public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// A selector match with the result of <see cref="SelectorMatchResult.AlwaysThisType"/>.
/// </summary>
/// <param name="match">The immediate match value.</param>
public SelectorMatch(bool match)
{
ImmediateResult = match;
}
public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// Gets a selector match with the result of <see cref="SelectorMatchResult.AlwaysThisInstance"/>.
/// </summary>
/// <param name="match">The observable match value.</param>
public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class with a
/// <see cref="SelectorMatchResult.Sometimes"/> result.
/// </summary>
/// <param name="match">The match activator.</param>
public SelectorMatch(IObservable<bool> match)
{
ObservableResult = match;
Contract.Requires<ArgumentNullException>(match != null);
Result = SelectorMatchResult.Sometimes;
Activator = match;
}
private SelectorMatch(SelectorMatchResult result) => Result = result;
/// <summary>
/// Gets the immediate result of the selector match, in the case of selectors that cannot
/// change over time.
/// Gets a value indicating whether the match was positive.
/// </summary>
public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType;
/// <summary>
/// Gets the result of the match.
/// </summary>
public bool? ImmediateResult { get; }
public SelectorMatchResult Result { 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; }
public IObservable<bool> Activator { get; }
}
}

24
src/Avalonia.Styling/Styling/Style.cs

@ -106,20 +106,14 @@ namespace Avalonia.Styling
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0;
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
/// <param name="control">The control to attach to.</param>
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public void Attach(IStyleable control, IStyleHost container)
/// <inheritdoc/>
public bool Attach(IStyleable control, IStyleHost container)
{
if (Selector != null)
{
var match = Selector.Match(control);
if (match.ImmediateResult != false)
if (match.IsMatch)
{
var controlSubscriptions = GetSubscriptions(control);
@ -129,9 +123,10 @@ namespace Avalonia.Styling
{
foreach (var animation in Animations)
{
IObservable<bool> obsMatch = match.ObservableResult;
var obsMatch = match.Activator;
if (match.ImmediateResult == true)
if (match.Result == SelectorMatchResult.AlwaysThisType ||
match.Result == SelectorMatchResult.AlwaysThisInstance)
{
obsMatch = Observable.Return(true);
}
@ -143,13 +138,15 @@ namespace Avalonia.Styling
foreach (var setter in Setters)
{
var sub = setter.Apply(this, control, match.ObservableResult);
var sub = setter.Apply(this, control, match.Activator);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
}
return match.Result != SelectorMatchResult.NeverThisType;
}
else if (control == container)
{
@ -165,7 +162,10 @@ namespace Avalonia.Styling
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
return true;
}
return false;
}
/// <inheritdoc/>

45
src/Avalonia.Styling/Styling/Styles.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
@ -15,6 +16,7 @@ namespace Avalonia.Styling
{
private IResourceNode _parent;
private IResourceDictionary _resources;
private Dictionary<Type, List<IStyle>> _cache;
public Styles()
{
@ -34,6 +36,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged += SubResourceChanged;
_cache = null;
},
x =>
{
@ -49,6 +52,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged -= SubResourceChanged;
_cache = null;
},
() => { });
}
@ -97,11 +101,46 @@ namespace Avalonia.Styling
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public void Attach(IStyleable control, IStyleHost container)
public bool Attach(IStyleable control, IStyleHost container)
{
foreach (IStyle style in this)
if (_cache == null)
{
_cache = new Dictionary<Type, List<IStyle>>();
}
if (_cache.TryGetValue(control.StyleKey, out var cached))
{
if (cached != null)
{
foreach (var style in cached)
{
style.Attach(control, container);
}
return true;
}
return false;
}
else
{
style.Attach(control, container);
List<IStyle> result = null;
foreach (var style in this)
{
if (style.Attach(control, container))
{
if (result == null)
{
result = new List<IStyle>();
}
result.Add(style);
}
}
_cache.Add(control.StyleKey, result);
return result != null;
}
}

5
src/Avalonia.Styling/Styling/TemplateSelector.cs

@ -23,6 +23,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => true;
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override Type TargetType => null;
@ -46,7 +49,7 @@ namespace Avalonia.Styling
"Cannot call Template selector on control with null TemplatedParent.");
}
return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True;
return _parent.Match(templatedParent, subscribe);
}
protected override Selector MovePrevious() => null;

19
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@ -68,6 +68,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override Type TargetType => _targetType ?? _previous?.TargetType;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <summary>
/// Whether the selector matches the concrete <see cref="TargetType"/> or any object which
/// implements <see cref="TargetType"/>.
@ -101,21 +104,23 @@ namespace Avalonia.Styling
{
if (controlType != TargetType)
{
return SelectorMatch.False;
return SelectorMatch.NeverThisType;
}
}
else
{
if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo()))
{
return SelectorMatch.False;
return SelectorMatch.NeverThisType;
}
}
}
if (Name != null && control.Name != Name)
if (Name != null)
{
return SelectorMatch.False;
return control.Name == Name ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance;
}
if (_classes.IsValueCreated && _classes.Value.Count > 0)
@ -127,12 +132,14 @@ namespace Avalonia.Styling
}
else
{
return new SelectorMatch(Matches(control.Classes));
return Matches(control.Classes) ?
SelectorMatch.AlwaysThisInstance :
SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.True;
return SelectorMatch.AlwaysThisType;
}
}

6
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
public void Attach(IStyleable control, IStyleHost container)
public bool Attach(IStyleable control, IStyleHost container)
{
if (Source != null)
{
Loaded.Attach(control, container);
return Loaded.Attach(control, container);
}
return false;
}
public void Detach()

8
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Child().OfType<TestLogical2>();
Assert.True(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@ -42,7 +42,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Child().OfType<TestLogical3>();
Assert.False(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(child).Result);
}
[Fact]
@ -54,7 +54,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Child().OfType<TestLogical2>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
var result = new List<bool>();
Assert.False(await activator.Take(1));
@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests
var control = new TestLogical3();
var selector = default(Selector).OfType<TestLogical1>().Child().OfType<TestLogical3>();
Assert.False(selector.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result);
}
[Fact]

23
tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs

@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var match = target.Match(control);
Assert.True(await activator.Take(1));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.True(await match.Activator.Take(1));
}
[Fact]
@ -54,9 +55,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var match = target.Match(control);
Assert.False(await activator.Take(1));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.False(await match.Activator.Take(1));
}
[Fact]
@ -69,9 +71,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var match = target.Match(control);
Assert.True(await activator.Take(1));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.True(await match.Activator.Take(1));
}
[Fact]
@ -80,7 +83,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1();
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@ -96,7 +99,7 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.True(await activator.Take(1));
control.Classes.Remove("foo");
@ -108,7 +111,7 @@ namespace Avalonia.Styling.UnitTests
{
var control = new Control1();
var target = default(Selector).Class("foo").Class("bar");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@ -129,7 +132,7 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
var result = new List<bool>();
using (activator.Subscribe(x => result.Add(x)))

10
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Descendant().OfType<TestLogical2>();
Assert.True(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType<TestLogical1>().Descendant().OfType<TestLogical3>();
Assert.True(selector.Match(child).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
Assert.True(await activator.Take(1));
}
@ -74,7 +74,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
Assert.False(await activator.Take(1));
}
@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType<TestLogical1>().Class("foo").Descendant().OfType<TestLogical3>();
var activator = selector.Match(child).ObservableResult;
var activator = selector.Match(child).Activator;
Assert.False(await activator.Take(1));
parent.Classes.Add("foo");

40
tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
public class SelectorTests_Multiple
{
[Fact]
public void Template_Child_Of_Control_With_Two_Classes()
public void Named_Template_Child_Of_Control_With_Two_Classes()
{
var template = new FuncControlTemplate(parent =>
{
@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests
var border = (Border)((IVisual)control).VisualChildren.Single();
var values = new List<bool>();
var activator = selector.Match(border).ObservableResult;
var match = selector.Match(border);
activator.Subscribe(x => values.Add(x));
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
match.Activator.Subscribe(x => values.Add(x));
Assert.Equal(new[] { false }, values);
control.Classes.AddRange(new[] { "foo", "bar" });
@ -51,6 +52,39 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new[] { false, true, false }, values);
}
[Fact]
public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type()
{
var template = new FuncControlTemplate(parent =>
{
return new Border
{
Name = "border",
};
});
var control = new Button
{
Template = template,
};
control.ApplyTemplate();
var selector = default(Selector)
.OfType<Button>()
.Class("foo")
.Class("bar")
.Template()
.OfType<TextBlock>()
.Name("baz");
var border = (Border)((IVisual)control).VisualChildren.Single();
var values = new List<bool>();
var match = selector.Match(border);
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
}
[Fact]
public void TargetType_OfType()
{

6
tests/Avalonia.Styling.UnitTests/SelectorTests_Name.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1 { Name = "foo" };
var target = default(Selector).Name("foo");
Assert.True(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, target.Match(control).Result);
}
[Fact]
@ -23,7 +23,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1 { Name = "foo" };
var target = default(Selector).Name("bar");
Assert.False(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(control).Result);
}
[Fact]
@ -33,7 +33,7 @@ namespace Avalonia.Styling.UnitTests
var target = default(Selector).Name("foo");
var activator = target.Match(control);
Assert.False(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, target.Match(control).Result);
}
[Fact]

15
tests/Avalonia.Styling.UnitTests/SelectorTests_OfType.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1();
var target = default(Selector).OfType<Control1>();
Assert.True(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
}
[Fact]
@ -23,7 +23,16 @@ namespace Avalonia.Styling.UnitTests
var control = new Control2();
var target = default(Selector).OfType<Control1>();
Assert.False(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
public void OfType_Class_Doesnt_Match_Control_Of_Wrong_Type()
{
var control = new Control2();
var target = default(Selector).OfType<Control1>().Class("foo");
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
@ -32,7 +41,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1 { TemplatedParent = new Mock<ITemplatedControl>().Object };
var target = default(Selector).OfType<Control1>();
Assert.True(target.Match(control).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
}
public class Control1 : TestControlBase

11
tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs

@ -16,7 +16,7 @@ namespace Avalonia.Styling.UnitTests
{
var control = new TextBlock();
var target = default(Selector).PropertyEquals(TextBlock.TextProperty, "foo");
var activator = target.Match(control).ObservableResult;
var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Text = "foo";
@ -25,6 +25,15 @@ namespace Avalonia.Styling.UnitTests
Assert.False(await activator.Take(1));
}
[Fact]
public void OfType_PropertyEquals_Doesnt_Match_Control_Of_Wrong_Type()
{
var control = new TextBlock();
var target = default(Selector).OfType<Border>().PropertyEquals(TextBlock.TextProperty, "foo");
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
public void PropertyEquals_Selector_Should_Have_Correct_String_Representation()
{

31
tests/Avalonia.Styling.UnitTests/SelectorTests_Template.cs

@ -29,7 +29,7 @@ namespace Avalonia.Styling.UnitTests
.Template()
.OfType<Border>();
Assert.True(selector.Match(border).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(border).Result);
}
[Fact]
@ -47,7 +47,24 @@ namespace Avalonia.Styling.UnitTests
.Template()
.OfType<Border>();
Assert.False(selector.Match(border).ImmediateResult);
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(border).Result);
}
[Fact]
public void Control_In_Template_Of_Wrong_Type_Is_Not_Matched_With_Template_Selector()
{
var target = new Mock<IVisual>();
var templatedControl = target.As<ITemplatedControl>();
var styleable = target.As<IStyleable>();
BuildVisualTree(target);
var border = (Border)target.Object.GetVisualChildren().Single();
var selector = default(Selector)
.OfType<Button>()
.Template()
.OfType<Border>();
Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(border).Result);
}
[Fact]
@ -64,7 +81,7 @@ namespace Avalonia.Styling.UnitTests
.Template()
.OfType<TextBlock>();
Assert.True(selector.Match(textBlock).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(textBlock).Result);
}
[Fact]
@ -80,7 +97,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType(styleKey).Template().OfType<Border>();
Assert.True(selector.Match(border).ImmediateResult);
Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(border).Result);
}
[Fact]
@ -96,7 +113,7 @@ namespace Avalonia.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("foo"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = default(Selector).OfType(styleKey).Class("foo").Template().OfType<Border>();
var activator = selector.Match(border).ObservableResult;
var activator = selector.Match(border).Activator;
Assert.True(await activator.Take(1));
}
@ -112,7 +129,7 @@ namespace Avalonia.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("bar"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = default(Selector).OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType<Border>();
var activator = selector.Match(border).ObservableResult;
var activator = selector.Match(border).Activator;
Assert.False(await activator.Take(1));
}
@ -128,7 +145,7 @@ namespace Avalonia.Styling.UnitTests
styleable.Setup(x => x.Classes).Returns(new Classes("foo"));
var border = (Border)target.Object.VisualChildren.Single();
var selector = default(Selector).OfType(templatedControl.Object.GetType()).Class("foo").Template().OfType<Border>();
var activator = selector.Match(border).ObservableResult;
var activator = selector.Match(border).Activator;
var inccDebug = (INotifyCollectionChangedDebug)styleable.Object.Classes;
using (activator.Subscribe(_ => { }))

Loading…
Cancel
Save