Browse Source

Additional validation for ControlTheme children.

pull/8263/head
Steven Kirk 4 years ago
parent
commit
8b4cf63be3
  1. 2
      src/Avalonia.Base/Styling/ChildSelector.cs
  2. 2
      src/Avalonia.Base/Styling/DescendentSelector.cs
  3. 2
      src/Avalonia.Base/Styling/NestingSelector.cs
  4. 2
      src/Avalonia.Base/Styling/NotSelector.cs
  5. 2
      src/Avalonia.Base/Styling/NthChildSelector.cs
  6. 12
      src/Avalonia.Base/Styling/OrSelector.cs
  7. 2
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  8. 31
      src/Avalonia.Base/Styling/Selector.cs
  9. 9
      src/Avalonia.Base/Styling/Style.cs
  10. 5
      src/Avalonia.Base/Styling/Styles.cs
  11. 2
      src/Avalonia.Base/Styling/TemplateSelector.cs
  12. 2
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  13. 64
      tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs

2
src/Avalonia.Base/Styling/ChildSelector.cs

@ -65,6 +65,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector();
protected override Selector? MovePreviousOrParent() => _parent;
}
}

2
src/Avalonia.Base/Styling/DescendentSelector.cs

@ -70,6 +70,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector();
protected override Selector? MovePreviousOrParent() => _parent;
}
}

2
src/Avalonia.Base/Styling/NestingSelector.cs

@ -33,6 +33,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => true;
protected override Selector? MovePreviousOrParent() => null;
}
}

2
src/Avalonia.Base/Styling/NotSelector.cs

@ -67,6 +67,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
protected override Selector? MovePreviousOrParent() => _previous;
}
}

2
src/Avalonia.Base/Styling/NthChildSelector.cs

@ -105,7 +105,7 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
protected override Selector? MovePreviousOrParent() => _previous;
public override string ToString()
{

12
src/Avalonia.Base/Styling/OrSelector.cs

@ -103,18 +103,12 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => null;
internal override bool HasValidNestingSelector()
internal override void ValidateNestingSelector(bool inControlTheme)
{
foreach (var selector in _selectors)
{
if (!selector.HasValidNestingSelector())
{
return false;
}
}
return true;
selector.ValidateNestingSelector(inControlTheme);
}
private Type? EvaluateTargetType()

2
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -90,7 +90,7 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
protected override Selector? MovePreviousOrParent() => _previous;
internal static bool Compare(Type propertyType, object? propertyValue, object? value)
{

31
src/Avalonia.Base/Styling/Selector.cs

@ -86,7 +86,36 @@ namespace Avalonia.Styling
/// </summary>
protected abstract Selector? MovePrevious();
internal abstract bool HasValidNestingSelector();
/// <summary>
/// Moves to the previous selector or the parent selector.
/// </summary>
protected abstract Selector? MovePreviousOrParent();
internal virtual void ValidateNestingSelector(bool inControlTheme)
{
var s = this;
var templateCount = 0;
do
{
if (inControlTheme)
{
if (!s.InTemplate && s.IsCombinator)
throw new InvalidOperationException(
"ControlTheme style may not directly contain a child or descendent selector.");
if (s is TemplateSelector && templateCount++ > 0)
throw new InvalidOperationException(
"ControlTemplate styles cannot contain multiple template selectors.");
}
var previous = s.MovePreviousOrParent();
if (previous is null && s is not NestingSelector)
throw new InvalidOperationException("Child styles must have a nesting selector.");
s = previous;
} while (s is not null);
}
private static SelectorMatch MatchUntilCombinator(
IStyleable control,

9
src/Avalonia.Base/Styling/Style.cs

@ -77,8 +77,13 @@ namespace Avalonia.Styling
{
if (Selector is null)
throw new InvalidOperationException("Child styles must have a selector.");
if (!Selector.HasValidNestingSelector())
throw new InvalidOperationException("Child styles must have a nesting selector.");
Selector.ValidateNestingSelector(false);
}
else if (parent is ControlTheme)
{
if (Selector is null)
throw new InvalidOperationException("Child styles must have a selector.");
Selector.ValidateNestingSelector(true);
}
base.SetParent(parent);

5
src/Avalonia.Base/Styling/Styles.cs

@ -26,6 +26,11 @@ namespace Avalonia.Styling
{
_styles.ResetBehavior = ResetBehavior.Remove;
_styles.CollectionChanged += OnCollectionChanged;
_styles.Validate = i =>
{
if (i is ControlTheme)
throw new InvalidOperationException("ControlThemes cannot be added to a Styles collection.");
};
}
public Styles(IResourceHost owner)

2
src/Avalonia.Base/Styling/TemplateSelector.cs

@ -49,6 +49,6 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => null;
internal override bool HasValidNestingSelector() => _parent?.HasValidNestingSelector() ?? false;
protected override Selector? MovePreviousOrParent() => _parent;
}
}

2
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -140,7 +140,7 @@ namespace Avalonia.Styling
}
protected override Selector? MovePrevious() => _previous;
internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false;
protected override Selector? MovePreviousOrParent() => _previous;
private string BuildSelectorString()
{

64
tests/Avalonia.Base.UnitTests/Styling/ControlThemeTests.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Styling;
using Xunit;
@ -7,6 +8,15 @@ namespace Avalonia.Base.UnitTests.Styling
{
public class ControlThemeTests
{
[Fact]
public void ControlTheme_Cannot_Be_Added_To_Styles()
{
var target = new ControlTheme(typeof(Button));
var styles = new Styles();
Assert.Throws<InvalidOperationException>(() => styles.Add(target));
}
[Fact]
public void ControlTheme_Cannot_Be_Added_To_Style_Children()
{
@ -24,5 +34,59 @@ namespace Avalonia.Base.UnitTests.Styling
Assert.Throws<InvalidOperationException>(() => other.Children.Add(target));
}
[Fact]
public void Style_Without_Selector_Cannot_Be_Added_To_Children()
{
var target = new ControlTheme(typeof(Button));
var child = new Style();
Assert.Throws<InvalidOperationException>(() => target.Children.Add(child));
}
[Fact]
public void Style_Without_Nesting_Selector_Cannot_Be_Added_To_Children()
{
var target = new ControlTheme(typeof(Button));
var child = new Style(x => x.OfType<Button>().Template().OfType<Border>());
Assert.Throws<InvalidOperationException>(() => target.Children.Add(child));
}
[Fact]
public void Style_With_NonTemplate_Child_Selector_Cannot_Be_Added_To_Children()
{
var target = new ControlTheme(typeof(Button));
var child = new Style(x => x.Nesting().Child().OfType<Border>());
Assert.Throws<InvalidOperationException>(() => target.Children.Add(child));
}
[Fact]
public void Style_With_NonTemplate_Descendent_Selector_Cannot_Be_Added_To_Children()
{
var target = new ControlTheme(typeof(Button));
var child = new Style(x => x.Nesting().Descendant().OfType<Border>());
Assert.Throws<InvalidOperationException>(() => target.Children.Add(child));
}
[Fact]
public void Style_With_NonTemplate_Child_Template_Selector_Cannot_Be_Added_To_Children()
{
var target = new ControlTheme(typeof(Button));
var child = new Style(x => x.Nesting().Child().Template().OfType<Border>());
Assert.Throws<InvalidOperationException>(() => target.Children.Add(child));
}
[Fact]
public void Style_With_Double_Template_Selector_Cannot_Be_Added_To_Children()
{
var target = new ControlTheme(typeof(Button));
var child = new Style(x => x.Nesting().Template().OfType<ToggleButton>().Template().OfType<Border>());
Assert.Throws<InvalidOperationException>(() => target.Children.Add(child));
}
}
}

Loading…
Cancel
Save