Browse Source

Added tests for ClassBindingManager.

Fixed already existing (IsClassesBindingProperty returning incorrect name) and newly added (Class.Foo="false" failing to override Classes="Foo Bar") bugs.
Changed anchor to nullable object to closer reflect underlying binding API.
pull/20182/head
Athari 7 months ago
parent
commit
8f4a7863de
  1. 25
      src/Avalonia.Base/ClassBindingManager.cs
  2. 4
      src/Avalonia.Base/StyledElementExtensions.cs
  3. 142
      tests/Avalonia.Base.UnitTests/ClassBindingManagerTests.cs

25
src/Avalonia.Base/ClassBindingManager.cs

@ -17,7 +17,7 @@ namespace Avalonia
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"Classes", typeof(ClassBindingManager), "");
public static readonly AttachedProperty<HashSet<string>?> BoundClassesProperty =
private static readonly AttachedProperty<HashSet<string>?> BoundClassesProperty =
AvaloniaProperty.RegisterAttached<StyledElement, HashSet<string>?>(
"BoundClasses", typeof(ClassBindingManager));
@ -33,13 +33,13 @@ namespace Avalonia
return element.GetValue(ClassesProperty);
}
public static void SetBoundClasses(StyledElement element, HashSet<string>? value)
private static void SetBoundClasses(StyledElement element, HashSet<string>? value)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(BoundClassesProperty, value);
}
public static HashSet<string>? GetBoundClasses(StyledElement element)
private static HashSet<string>? GetBoundClasses(StyledElement element)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(BoundClassesProperty);
@ -55,7 +55,9 @@ namespace Avalonia
var boundClasses = GetBoundClasses(sender);
var newValue = e.GetNewValue<string?>() ?? "";
var newValues = newValue.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var newValues = newValue.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Where(c => boundClasses?.Contains(c) != true)
.ToList();
var currentValues = sender.Classes
.Where(c => !c.StartsWith(":", StringComparison.Ordinal) && boundClasses?.Contains(c) != true)
.ToList();
@ -76,9 +78,9 @@ namespace Avalonia
boundClasses.Add(className);
}
public static IDisposable BindClasses(StyledElement target, BindingBase source, object anchor)
public static IDisposable BindClasses(StyledElement target, BindingBase source, object? anchor)
{
return target.Bind(ClassesProperty, source);
return target.Bind(ClassesProperty, source, anchor);
}
public static void SetClass(StyledElement target, string className, bool value)
@ -87,11 +89,11 @@ namespace Avalonia
target.Classes.Set(className, value);
}
public static IDisposable BindClass(StyledElement target, string className, BindingBase source, object anchor)
public static IDisposable BindClass(StyledElement target, string className, BindingBase source, object? anchor)
{
AddBoundClass(target, className);
var prop = GetClassProperty(className);
return target.Bind(prop, source);
return target.Bind(prop, source, anchor);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("AvaloniaProperty", "AVP1001:The same AvaloniaProperty should not be registered twice",
@ -120,11 +122,10 @@ namespace Avalonia
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static bool IsClassesBindingProperty(AvaloniaProperty property, [NotNullWhen(true)] out string? classPropertyName)
{
classPropertyName = default;
if(property.Name?.StartsWith(ClassPropertyPrefix, StringComparison.OrdinalIgnoreCase) == true)
classPropertyName = null;
if (property.Name?.StartsWith(ClassPropertyPrefix, StringComparison.OrdinalIgnoreCase) == true)
{
classPropertyName = property.Name.Substring(ClassPropertyPrefix.Length + 1);
classPropertyName = property.Name.Substring(ClassPropertyPrefix.Length);
return true;
}
return false;

4
src/Avalonia.Base/StyledElementExtensions.cs

@ -6,13 +6,13 @@ namespace Avalonia
{
public static class StyledElementExtensions
{
public static IDisposable BindClasses(this StyledElement target, BindingBase source, object anchor) =>
public static IDisposable BindClasses(this StyledElement target, BindingBase source, object? anchor = null) =>
ClassBindingManager.BindClasses(target, source, anchor);
public static void SetClasses(this StyledElement target, string classNames) =>
ClassBindingManager.SetClasses(target, classNames);
public static IDisposable BindClass(this StyledElement target, string className, BindingBase source, object anchor) =>
public static IDisposable BindClass(this StyledElement target, string className, BindingBase source, object? anchor = null) =>
ClassBindingManager.BindClass(target, className, source, anchor);
public static void SetClass(this StyledElement target, string className, bool value) =>

142
tests/Avalonia.Base.UnitTests/ClassBindingManagerTests.cs

@ -1,15 +1,17 @@
using Xunit;
using Avalonia.Data;
using Avalonia.Controls;
using Xunit;
namespace Avalonia.Base.UnitTests
{
public class ClassBindingManagerTests
{
[Fact]
public void GetClassProperty_Should_Return_Same_Instance_For_Same_Class()
{
var property1 = ClassBindingManager.GetClassProperty("Foo");
var property2 = ClassBindingManager.GetClassProperty("Foo");
Assert.Same(property1, property2);
}
@ -18,7 +20,143 @@ namespace Avalonia.Base.UnitTests
{
var property1 = ClassBindingManager.GetClassProperty("Foo");
var property2 = ClassBindingManager.GetClassProperty("Bar");
Assert.NotSame(property1, property2);
}
[Fact]
public void SetClass_Should_Add_Class()
{
var target = new StyledElement();
ClassBindingManager.SetClass(target, "Foo", true);
Assert.Contains("Foo", target.Classes);
}
[Fact]
public void SetClass_Should_Remove_Added_Class()
{
var target = new StyledElement();
ClassBindingManager.SetClass(target, "Foo", true);
ClassBindingManager.SetClass(target, "Foo", false);
Assert.DoesNotContain("Foo", target.Classes);
}
[Fact]
public void SetClasses_Should_Add_Classes()
{
var target = new StyledElement();
ClassBindingManager.SetClasses(target, "Foo Bar");
Assert.Contains("Foo", target.Classes);
Assert.Contains("Bar", target.Classes);
}
[Fact]
public void SetClasses_Should_Remove_Added_Classes()
{
var target = new StyledElement();
ClassBindingManager.SetClasses(target, "Foo Bar");
ClassBindingManager.SetClasses(target, "");
Assert.Empty(target.Classes);
}
[Fact]
public void SetClasses_Should_Keep_PseudoClasses()
{
var target = new StyledElement();
((IPseudoClasses)target.Classes).Add(":Baz");
ClassBindingManager.SetClasses(target, "Foo");
Assert.Equal(new[] { ":Baz", "Foo" }, target.Classes);
}
[Fact]
public void SetClass_Should_Override_SetClasses_Adding()
{
var target = new StyledElement();
ClassBindingManager.SetClasses(target, "Foo Bar");
ClassBindingManager.SetClass(target, "Foo", false);
Assert.Contains("Bar", target.Classes);
Assert.DoesNotContain("Foo", target.Classes);
}
[Fact]
public void SetClass_Should_Override_SetClasses_Adding_When_Set_Before()
{
var target = new StyledElement();
ClassBindingManager.SetClass(target, "Foo", false);
ClassBindingManager.SetClasses(target, "Foo Bar");
Assert.Contains("Bar", target.Classes);
Assert.DoesNotContain("Foo", target.Classes);
}
[Fact]
public void SetClass_Should_Override_SetClasses_Removing()
{
var target = new StyledElement();
ClassBindingManager.SetClasses(target, "Bar");
ClassBindingManager.SetClass(target, "Foo", true);
Assert.Contains("Foo", target.Classes);
Assert.Contains("Bar", target.Classes);
}
[Fact]
public void SetClass_Should_Override_SetClasses_Removing_When_Set_Before()
{
var target = new StyledElement();
ClassBindingManager.SetClass(target, "Foo", true);
ClassBindingManager.SetClasses(target, "Bar");
Assert.Contains("Foo", target.Classes);
Assert.Contains("Bar", target.Classes);
}
[Fact]
public void BindClass_Should_Update_Classes()
{
var target = new StyledElement();
using var d = ClassBindingManager.BindClass(target, "Bar", new Binding { Source = true }, null);
Assert.Contains("Bar", target.Classes);
}
[Fact]
public void BindClasses_Should_Update_Classes()
{
var target = new StyledElement();
using var d = ClassBindingManager.BindClasses(target, new Binding { Source = "Foo Bar" }, null);
Assert.Equal("Foo Bar", ClassBindingManager.GetClasses(target));
Assert.Contains("Foo", target.Classes);
Assert.Contains("Bar", target.Classes);
}
[Fact]
public void IsClassesBindingProperty_Should_Detect_Classes_Properties()
{
var prop = ClassBindingManager.GetClassProperty("Foo");
var result = ClassBindingManager.IsClassesBindingProperty(prop, out var name);
Assert.True(result);
Assert.Equal("Foo", name);
}
}
}

Loading…
Cancel
Save