Files
2026-07-31 23:45:25 +08:00

931 lines
33 KiB
C#

using System.Reflection;
using AtomUI.Controls;
using AtomUI.Controls.Primitives;
using AtomUI.Theme;
using AtomUI.Theme.Configuration;
using AtomUI.Theme.Resources;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Shouldly;
using Xunit;
using AtomUIButton = AtomUI.Desktop.Controls.Button;
using AvaloniaWindow = Avalonia.Controls.Window;
namespace AtomUI.Desktop.Controls.Tests.Buttons;
public class ButtonBehaviorTests
{
static ButtonBehaviorTests()
{
AvaloniaTestApp.EnsureInitialized();
}
[Fact]
public void Button_ApplyTemplate_Configures_WaveSpiritType_From_Current_Shape()
{
var button = new AtomUIButton
{
Shape = ButtonShape.Circle,
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue(button, "WaveSpiritType")
.ToString()
.ShouldBe("CircleWave");
});
}
[Fact]
public void Button_ApplyTemplate_Configures_EffectiveBorderThickness_From_Current_Type()
{
var borderThickness = new Thickness(4);
var button = new AtomUIButton
{
ButtonType = ButtonType.Link,
BorderThickness = borderThickness,
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(new Thickness(0));
});
}
[Fact]
public void Button_ButtonType_Change_Refreshes_EffectiveBorderThickness()
{
var borderThickness = new Thickness(3);
var button = new AtomUIButton
{
ButtonType = ButtonType.Link,
BorderThickness = borderThickness,
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(new Thickness(0));
button.ButtonType = ButtonType.Default;
Dispatcher.UIThread.RunJobs();
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(borderThickness);
button.ButtonType = ButtonType.Text;
Dispatcher.UIThread.RunJobs();
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(new Thickness(0));
});
}
[Theory]
[InlineData(ButtonType.Default, false, "Default", "Outlined", true)]
[InlineData(ButtonType.Dashed, false, "Default", "Dashed", true)]
[InlineData(ButtonType.Primary, false, "Primary", "Solid", true)]
[InlineData(ButtonType.Text, false, "Default", "Text", false)]
[InlineData(ButtonType.Link, false, "Default", "Link", false)]
[InlineData(ButtonType.Default, true, "Danger", "Outlined", true)]
[InlineData(ButtonType.Primary, true, "Danger", "Solid", true)]
[InlineData(ButtonType.Text, true, "Danger", "Text", false)]
[InlineData(ButtonType.Link, true, "Danger", "Link", false)]
public void Button_Compatibility_Apis_Configure_Effective_Color_And_Variant(
ButtonType buttonType,
bool isDanger,
string expectedColor,
string expectedVariant,
bool expectedBordered)
{
var borderThickness = new Thickness(2);
var button = new AtomUIButton
{
ButtonType = buttonType,
IsDanger = isDanger,
BorderThickness = borderThickness,
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue(button, "EffectiveColor")
.ToString()
.ShouldBe(expectedColor);
GetInternalPropertyValue(button, "EffectiveVariant")
.ToString()
.ShouldBe(expectedVariant);
GetInternalPropertyValue<bool>(button, "EffectiveIsBordered")
.ShouldBe(expectedBordered);
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(expectedBordered ? borderThickness : new Thickness(0));
});
}
[Theory]
[InlineData("default")]
[InlineData("primary")]
[InlineData("danger")]
public void Button_Text_Variant_Uses_Expected_Semantic_State_Colors(string scenario)
{
var button = CreateTextVariantButton(scenario);
ShowInWindow(button, () =>
{
var expected = scenario switch
{
"default" => new TextVariantColors(
SharedTokenKind.ColorText,
SharedTokenKind.ColorText,
SharedTokenKind.ColorText,
SharedTokenKind.ColorFillTertiary,
SharedTokenKind.ColorFill),
"primary" => new TextVariantColors(
SharedTokenKind.ColorPrimary,
SharedTokenKind.ColorPrimaryHover,
SharedTokenKind.ColorPrimaryActive,
SharedTokenKind.ColorPrimaryBg,
SharedTokenKind.ColorPrimaryBorder),
"danger" => new TextVariantColors(
SharedTokenKind.ColorError,
SharedTokenKind.ColorErrorHover,
SharedTokenKind.ColorErrorActive,
SharedTokenKind.ColorErrorBg,
SharedTokenKind.ColorErrorBgActive),
_ => throw new ArgumentOutOfRangeException(nameof(scenario), scenario, null)
};
BrushShouldHaveSameColor(button.Foreground, GetThemeResource<IBrush>(expected.Text));
BrushShouldBeTransparent(button.Background);
SetPseudoClass(button, StdPseudoClass.PointerOver, true);
Dispatcher.UIThread.RunJobs();
BrushShouldHaveSameColor(button.Foreground, GetThemeResource<IBrush>(expected.TextHover));
BrushShouldHaveSameColor(button.Background, GetThemeResource<IBrush>(expected.BackgroundHover));
SetPseudoClass(button, StdPseudoClass.Pressed, true);
Dispatcher.UIThread.RunJobs();
BrushShouldHaveSameColor(button.Foreground, GetThemeResource<IBrush>(expected.TextPressed));
BrushShouldHaveSameColor(button.Background, GetThemeResource<IBrush>(expected.BackgroundPressed));
});
}
[Theory]
[InlineData(ButtonType.Default, false, false)]
[InlineData(ButtonType.Dashed, false, false)]
[InlineData(ButtonType.Primary, false, false)]
[InlineData(ButtonType.Link, false, false)]
[InlineData(ButtonType.Text, false, false)]
[InlineData(ButtonType.Default, true, false)]
[InlineData(ButtonType.Primary, true, false)]
[InlineData(ButtonType.Text, true, false)]
[InlineData(ButtonType.Default, false, true)]
[InlineData(ButtonType.Primary, false, true)]
public void Button_Compatibility_Visuals_Project_Effective_Variant_Colors(
ButtonType buttonType,
bool isDanger,
bool isGhost)
{
var button = new AtomUIButton
{
ButtonType = buttonType,
IsDanger = isDanger,
IsGhost = isGhost,
Content = "Button",
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
BrushShouldHaveSameColor(
button.Foreground,
GetInternalPropertyValue<IBrush?>(button, "VariantTextBrush"));
BrushShouldHaveSameColor(
button.Background,
GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush"));
BrushShouldHaveSameColor(
button.BorderBrush,
GetInternalPropertyValue<IBrush?>(button, "VariantBorderBrush"));
SetPseudoClass(button, StdPseudoClass.PointerOver, true);
Dispatcher.UIThread.RunJobs();
BrushShouldHaveSameColor(
button.Foreground,
GetInternalPropertyValue<IBrush?>(button, "VariantTextHoverBrush"));
BrushShouldHaveSameColor(
button.Background,
GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundHoverBrush"));
BrushShouldHaveSameColor(
button.BorderBrush,
GetInternalPropertyValue<IBrush?>(button, "VariantBorderHoverBrush"));
SetPseudoClass(button, StdPseudoClass.Pressed, true);
Dispatcher.UIThread.RunJobs();
BrushShouldHaveSameColor(
button.Foreground,
GetInternalPropertyValue<IBrush?>(button, "VariantTextPressedBrush"));
BrushShouldHaveSameColor(
button.Background,
GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundPressedBrush"));
BrushShouldHaveSameColor(
button.BorderBrush,
GetInternalPropertyValue<IBrush?>(button, "VariantBorderPressedBrush"));
});
}
[Fact]
public void Button_Explicit_Color_And_Variant_Override_ButtonType_And_Danger()
{
var button = new AtomUIButton
{
ButtonType = ButtonType.Primary,
IsDanger = true,
Color = ButtonColor.Pink,
Variant = ButtonVariant.Filled,
BorderThickness = new Thickness(2),
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue<ButtonColor>(button, "EffectiveColor")
.ShouldBe(ButtonColor.Pink);
GetInternalPropertyValue<ButtonVariant>(button, "EffectiveVariant")
.ShouldBe(ButtonVariant.Filled);
GetInternalPropertyValue<bool>(button, "EffectiveIsBordered")
.ShouldBeFalse();
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(new Thickness(0));
});
}
[Fact]
public void Button_IconPlacement_Defaults_To_Start_And_Can_Move_Icon_To_End()
{
var iconPlacementProperty = typeof(AtomUIButton).GetProperty(
"IconPlacement",
BindingFlags.Instance | BindingFlags.Public);
iconPlacementProperty.ShouldNotBeNull();
iconPlacementProperty!.PropertyType.IsEnum.ShouldBeTrue();
Enum.GetNames(iconPlacementProperty.PropertyType).ShouldBe(["Start", "End"], ignoreOrder: false);
var startButton = CreateIconPlacementButton();
var endButton = CreateIconPlacementButton();
var endValue = Enum.Parse(iconPlacementProperty.PropertyType, "End");
iconPlacementProperty.SetValue(endButton, endValue);
var host = new StackPanel
{
Children =
{
startButton,
endButton
}
};
ShowInWindow(host, () =>
{
iconPlacementProperty.GetValue(startButton)!.ToString().ShouldBe("Start");
var startIcon = FindTemplateIcon(startButton);
var endIcon = FindTemplateIcon(endButton);
DockPanel.GetDock(startIcon).ShouldBe(Dock.Left);
DockPanel.GetDock(endIcon).ShouldBe(Dock.Right);
endIcon.Margin.Left.ShouldBe(startIcon.Margin.Right);
endIcon.Margin.Right.ShouldBe(startIcon.Margin.Left);
});
}
[Fact]
public void Button_Solid_Variant_Without_Color_Uses_Primary_Color()
{
var button = new AtomUIButton
{
Variant = ButtonVariant.Solid,
BorderThickness = new Thickness(2),
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue<ButtonColor>(button, "EffectiveColor")
.ShouldBe(ButtonColor.Primary);
GetInternalPropertyValue<ButtonVariant>(button, "EffectiveVariant")
.ShouldBe(ButtonVariant.Solid);
GetInternalPropertyValue<bool>(button, "EffectiveIsBordered")
.ShouldBeTrue();
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(button.BorderThickness);
});
}
[Fact]
public void Button_Default_Color_And_Solid_Variant_Uses_Default_Solid_Surface()
{
var button = new AtomUIButton
{
Color = ButtonColor.Default,
Variant = ButtonVariant.Solid,
BorderThickness = new Thickness(2),
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
button.GetValue(ThemeScope.ContextProperty).ShouldNotBeNull();
GetInternalPropertyValue<ButtonColor>(button, "EffectiveColor")
.ShouldBe(ButtonColor.Default);
GetInternalPropertyValue<ButtonVariant>(button, "EffectiveVariant")
.ShouldBe(ButtonVariant.Solid);
GetInternalPropertyValue<bool>(button, "EffectiveIsBordered")
.ShouldBeTrue();
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(button.BorderThickness);
BrushShouldHaveSameColor(button.Foreground, GetInternalPropertyValue<IBrush?>(button, "VariantTextBrush"));
BrushShouldHaveSameColor(button.Background, GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush"));
var foreground = GetSolidBrushColor(button.Foreground);
var background = GetSolidBrushColor(button.Background);
background.A.ShouldBe((byte)255);
foreground.ShouldNotBe(background);
});
}
[Theory]
[InlineData("button-type-primary")]
[InlineData("explicit-primary-solid")]
public void Button_Primary_Variant_Background_Follows_Scoped_ColorPrimary(
string scenario)
{
var scopedPrimaryColor = Color.Parse("#00A1D6");
var button = CreateScopedPrimaryButton(scenario);
var provider = new ThemeConfigProvider
{
Config = new ThemeConfigBuilder()
.WithToken("ColorPrimary", "#00A1D6")
.Build(),
Child = button
};
ShowInWindow(provider, () =>
{
BrushShouldHaveColor(button.Background, scopedPrimaryColor);
BrushShouldHaveColor(GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush"),
scopedPrimaryColor);
BrushShouldHaveSameColor(button.Background,
GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush"));
});
}
[Fact]
public void Button_Ghost_Solid_Variant_Becomes_Outlined()
{
var button = new AtomUIButton
{
Color = ButtonColor.Primary,
Variant = ButtonVariant.Solid,
IsGhost = true,
BorderThickness = new Thickness(2),
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
GetInternalPropertyValue<ButtonColor>(button, "EffectiveColor")
.ShouldBe(ButtonColor.Primary);
GetInternalPropertyValue<ButtonVariant>(button, "EffectiveVariant")
.ShouldBe(ButtonVariant.Outlined);
GetInternalPropertyValue<bool>(button, "EffectiveIsBordered")
.ShouldBeTrue();
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(button.BorderThickness);
});
}
[Theory]
[InlineData(ButtonVariant.Text)]
[InlineData(ButtonVariant.Link)]
public void Button_Text_And_Link_Variants_Keep_Normal_Surface_Transparent_And_Shadowless(
ButtonVariant variant)
{
var button = new AtomUIButton
{
Color = ButtonColor.Pink,
Variant = variant,
BorderThickness = new Thickness(2),
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
BrushShouldBeTransparent(button.Background);
BrushShouldBeTransparent(GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush"));
GetInternalPropertyValue<BoxShadows>(button, "VariantShadow")
.ShouldBe(new BoxShadows());
GetInternalPropertyValue<Thickness>(button, "EffectiveBorderThickness")
.ShouldBe(new Thickness(0));
var shadowFrame = FindTemplateBorder(button, "ShadowsFrame");
BrushShouldBeTransparent(shadowFrame.Background);
shadowFrame.BoxShadow.ShouldBe(new BoxShadows());
});
}
[Theory]
[InlineData("primary-solid", true)]
[InlineData("primary-outlined", false)]
[InlineData("primary-text", false)]
[InlineData("danger-solid", false)]
[InlineData("disabled-primary-solid", false)]
public void Button_CustomBackground_Visibility_Follows_Effective_State(
string scenario,
bool expectedVisible)
{
var customBackground = CreateCustomBackground();
var button = CreateCustomBackgroundButton(scenario, customBackground);
ShowInWindow(button, () =>
{
GetInternalPropertyValue<bool>(button, "HasCustomBackground")
.ShouldBe(expectedVisible);
var customBackgroundLayer = FindTemplateBorder(button, "CustomBackgroundLayer");
customBackgroundLayer.Background.ShouldBeSameAs(customBackground);
customBackgroundLayer.IsVisible.ShouldBe(expectedVisible);
customBackgroundLayer.Opacity.ShouldBe(expectedVisible ? 1.0 : 0.0);
});
}
[Theory]
[InlineData(ButtonColor.Pink, ButtonVariant.Solid, "VariantBackgroundBrush")]
[InlineData(ButtonColor.Pink, ButtonVariant.Outlined, "VariantBorderBrush")]
[InlineData(ButtonColor.Cyan, ButtonVariant.Dashed, "VariantBorderBrush")]
public void Button_WaveSpiritBrush_Follows_Effective_Color_And_Variant(
ButtonColor color,
ButtonVariant variant,
string expectedBrushProperty)
{
var button = new AtomUIButton
{
Color = color,
Variant = variant,
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
var waveSpiritDecorator = GetPrivateFieldValue(button, "_waveSpiritDecorator");
var waveBrush = GetPublicPropertyValue<IBrush?>(waveSpiritDecorator, "WaveBrush");
var expectedBrush = GetInternalPropertyValue<IBrush?>(button, expectedBrushProperty);
BrushShouldHaveSameColor(waveBrush, expectedBrush);
});
}
[Fact]
public void Button_CustomBackground_Does_Not_Change_WaveSpiritBrush()
{
var customBackground = CreateCustomBackground();
var button = new AtomUIButton
{
ButtonType = ButtonType.Primary,
CustomBackground = customBackground,
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
var waveSpiritDecorator = GetPrivateFieldValue(button, "_waveSpiritDecorator");
var waveBrush = GetPublicPropertyValue<IBrush?>(waveSpiritDecorator, "WaveBrush");
var expectedBrush = GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush");
waveBrush.ShouldNotBeSameAs(customBackground);
BrushShouldHaveSameColor(waveBrush, expectedBrush);
});
}
[Fact]
public void Button_WaveRange_Update_Reconfigures_Cached_WavePainter()
{
var button = new AtomUIButton
{
Width = 120,
Height = 36,
ButtonType = ButtonType.Primary,
Content = "Save",
IsMotionEnabled = true
};
ShowInWindow(button, () =>
{
var waveSpiritDecorator = GetPrivateFieldValue(button, "_waveSpiritDecorator");
SetPublicPropertyValue(waveSpiritDecorator, "SizeMotionDuration", TimeSpan.FromSeconds(1));
SetPublicPropertyValue(waveSpiritDecorator, "OpacityMotionDuration", TimeSpan.FromSeconds(1));
SetPublicPropertyValue(waveSpiritDecorator, "WaveRange", 0d);
InvokePublicMethod(waveSpiritDecorator, "Play");
var wavePainter = GetPrivateFieldValue(waveSpiritDecorator, "_wavePainter");
GetPublicPropertyValue<double>(wavePainter, "WaveRange").ShouldBe(0d);
SetPublicPropertyValue(waveSpiritDecorator, "WaveRange", 6d);
GetPublicPropertyValue<double>(wavePainter, "WaveRange").ShouldBe(6d);
});
}
[Theory]
[InlineData("motion")]
[InlineData("wave-spirit")]
public void Button_Disabling_Wave_Gates_Cancels_Active_Wave(string gate)
{
var button = new AtomUIButton
{
Width = 120,
Height = 36,
ButtonType = ButtonType.Primary,
Content = "Save",
IsMotionEnabled = true,
IsWaveSpiritEnabled = true
};
ShowInWindow(button, () =>
{
var waveSpiritDecorator = GetPrivateFieldValue(button, "_waveSpiritDecorator");
SetPublicPropertyValue(waveSpiritDecorator, "SizeMotionDuration", TimeSpan.FromSeconds(1));
SetPublicPropertyValue(waveSpiritDecorator, "OpacityMotionDuration", TimeSpan.FromSeconds(1));
InvokePublicMethod(waveSpiritDecorator, "Play");
GetInternalPropertyValue<bool>(waveSpiritDecorator, "IsPlaying").ShouldBeTrue();
if (gate == "motion")
{
button.IsMotionEnabled = false;
}
else
{
button.IsWaveSpiritEnabled = false;
}
Dispatcher.UIThread.RunJobs();
GetInternalPropertyValue<bool>(waveSpiritDecorator, "IsPlaying").ShouldBeFalse();
});
}
[Fact]
public void Button_CustomBackground_Layer_Overlays_Frame_In_Normal_State()
{
var button = new AtomUIButton
{
ButtonType = ButtonType.Primary,
CustomBackground = CreateCustomBackground(),
IsMotionEnabled = false
};
ShowInWindow(button, () =>
{
var frame = FindTemplateDashedBorder(button, "Frame");
var customBackgroundLayer = FindTemplateBorder(button, "CustomBackgroundLayer");
var rootPanel = frame.GetVisualParent<Panel>();
rootPanel.ShouldNotBeNull();
var rootChildren = rootPanel!.GetVisualChildren().ToList();
var frameIndex = rootChildren.IndexOf(frame);
var customBackgroundLayerIndex = rootChildren.IndexOf(customBackgroundLayer);
frameIndex.ShouldBeGreaterThanOrEqualTo(0);
customBackgroundLayerIndex.ShouldBeGreaterThan(frameIndex);
BrushShouldHaveSameColor(frame.Background, GetInternalPropertyValue<IBrush?>(button, "VariantBackgroundBrush"));
});
}
[Fact]
public void Button_Default_Frame_Uses_AtomUI_Border_Rendering()
{
var button = new AtomUIButton
{
ButtonType = ButtonType.Default,
BorderThickness = new Thickness(1),
BorderBrush = Brushes.Black,
IsMotionEnabled = false
};
ShowInWindow(button, window =>
{
window.SetRenderScaling(1.5);
Dispatcher.UIThread.RunJobs();
var frame = FindTemplateDashedBorder(button, "Frame");
RenderToDrawingGroup(frame);
GetBorderRenderHelperThickness(frame).ShouldBe(new Thickness(2d / 3d));
});
}
private static AtomUIButton CreateCustomBackgroundButton(string scenario, IBrush customBackground)
{
var button = new AtomUIButton
{
CustomBackground = customBackground,
IsMotionEnabled = false
};
switch (scenario)
{
case "primary-solid":
button.ButtonType = ButtonType.Primary;
break;
case "primary-outlined":
button.Color = ButtonColor.Primary;
button.Variant = ButtonVariant.Outlined;
break;
case "primary-text":
button.Color = ButtonColor.Primary;
button.Variant = ButtonVariant.Text;
break;
case "danger-solid":
button.ButtonType = ButtonType.Primary;
button.IsDanger = true;
break;
case "disabled-primary-solid":
button.ButtonType = ButtonType.Primary;
button.IsEnabled = false;
break;
default:
throw new ArgumentOutOfRangeException(nameof(scenario), scenario, null);
}
return button;
}
private static AtomUIButton CreateTextVariantButton(string scenario)
{
var button = new AtomUIButton
{
Content = "Text",
IsMotionEnabled = false
};
switch (scenario)
{
case "default":
button.ButtonType = ButtonType.Text;
break;
case "primary":
button.Color = ButtonColor.Primary;
button.Variant = ButtonVariant.Text;
break;
case "danger":
button.ButtonType = ButtonType.Text;
button.IsDanger = true;
break;
default:
throw new ArgumentOutOfRangeException(nameof(scenario), scenario, null);
}
return button;
}
private static AtomUIButton CreateScopedPrimaryButton(string scenario)
{
var button = new AtomUIButton
{
IsMotionEnabled = false
};
switch (scenario)
{
case "button-type-primary":
button.ButtonType = ButtonType.Primary;
break;
case "explicit-primary-solid":
button.Color = ButtonColor.Primary;
button.Variant = ButtonVariant.Solid;
break;
default:
throw new ArgumentOutOfRangeException(nameof(scenario), scenario, null);
}
return button;
}
private static LinearGradientBrush CreateCustomBackground()
{
return new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.MediumPurple, Offset = 0 },
new GradientStop { Color = Colors.DeepSkyBlue, Offset = 1 }
}
};
}
private static AtomUIButton CreateIconPlacementButton()
{
return new AtomUIButton
{
Content = "Search",
Icon = new PathIcon { Data = Geometry.Parse("M0,0 L10,0 L10,10 Z") },
IsMotionEnabled = false
};
}
private static object GetInternalPropertyValue(object target, string propertyName)
{
var property = target.GetType().GetProperty(
propertyName,
BindingFlags.Instance | BindingFlags.NonPublic);
property.ShouldNotBeNull();
var value = property.GetValue(target);
value.ShouldNotBeNull();
return value;
}
private static T GetInternalPropertyValue<T>(object target, string propertyName)
{
return (T)GetInternalPropertyValue(target, propertyName);
}
private static object GetPrivateFieldValue(object target, string fieldName)
{
var field = target.GetType().GetField(
fieldName,
BindingFlags.Instance | BindingFlags.NonPublic);
field.ShouldNotBeNull();
var value = field.GetValue(target);
value.ShouldNotBeNull();
return value;
}
private static T GetPublicPropertyValue<T>(object target, string propertyName)
{
var property = target.GetType().GetProperty(
propertyName,
BindingFlags.Instance | BindingFlags.Public);
property.ShouldNotBeNull();
return (T)property.GetValue(target)!;
}
private static void SetPublicPropertyValue<T>(object target, string propertyName, T value)
{
var property = target.GetType().GetProperty(
propertyName,
BindingFlags.Instance | BindingFlags.Public);
property.ShouldNotBeNull();
property.SetValue(target, value);
}
private static void InvokePublicMethod(object target, string methodName)
{
var method = target.GetType().GetMethod(
methodName,
BindingFlags.Instance | BindingFlags.Public);
method.ShouldNotBeNull();
method.Invoke(target, null);
}
private static Border FindTemplateBorder(Control control, string name)
{
var border = control.GetVisualDescendants()
.OfType<Border>()
.SingleOrDefault(item => item.Name == name);
border.ShouldNotBeNull();
return border;
}
private static DashedBorder FindTemplateDashedBorder(Control control, string name)
{
var border = control.GetVisualDescendants()
.OfType<DashedBorder>()
.SingleOrDefault(item => item.Name == name);
border.ShouldNotBeNull();
return border!;
}
private static DrawingGroup RenderToDrawingGroup(Control control)
{
var drawingGroup = new DrawingGroup();
using (var context = drawingGroup.Open())
{
control.Render(context);
}
return drawingGroup;
}
private static Thickness GetBorderRenderHelperThickness(DashedBorder border)
{
var helperField = typeof(DashedBorder).GetField(
"_borderRenderHelper",
BindingFlags.Instance | BindingFlags.NonPublic);
helperField.ShouldNotBeNull();
var helper = helperField!.GetValue(border);
helper.ShouldNotBeNull();
var thicknessField = helper!.GetType().GetField(
"_borderThickness",
BindingFlags.Instance | BindingFlags.NonPublic);
thicknessField.ShouldNotBeNull();
return (Thickness)thicknessField!.GetValue(helper)!;
}
private static IconPresenter FindTemplateIcon(Control control)
{
var icon = control.GetVisualDescendants()
.OfType<IconPresenter>()
.SingleOrDefault(item => item.Name == "PART_ButtonIcon");
icon.ShouldNotBeNull();
return icon!;
}
private static void BrushShouldBeTransparent(IBrush? brush)
{
if (brush is null)
{
return;
}
GetSolidBrushColor(brush).A.ShouldBe((byte)0);
}
private static void BrushShouldHaveSameColor(IBrush? actual, IBrush? expected)
{
GetSolidBrushColor(actual).ShouldBe(GetSolidBrushColor(expected));
}
private static void BrushShouldHaveColor(IBrush? actual, Color expected)
{
GetSolidBrushColor(actual).ShouldBe(expected);
}
private static T GetThemeResource<T>(object key)
{
var application = Application.Current;
application.ShouldNotBeNull();
application!.TryGetResource(key, application.ActualThemeVariant, out var value).ShouldBeTrue();
value.ShouldBeAssignableTo<T>();
return (T)value!;
}
private static void SetPseudoClass(Control control, string pseudoClass, bool value)
{
((IPseudoClasses)control.Classes).Set(pseudoClass, value);
}
private static Color GetSolidBrushColor(IBrush? brush)
{
brush.ShouldNotBeNull();
brush.ShouldBeAssignableTo<ISolidColorBrush>();
return ((ISolidColorBrush)brush!).Color;
}
private static void ShowInWindow(Control content, Action assertion)
{
ShowInWindow(content, _ => assertion());
}
private static void ShowInWindow(Control content, Action<AvaloniaWindow> assertion)
{
var window = new AvaloniaWindow
{
Width = 240,
Height = 160,
Content = content
};
try
{
window.Show();
Dispatcher.UIThread.RunJobs();
assertion(window);
}
finally
{
window.Close();
}
}
private sealed record TextVariantColors(
SharedTokenKind Text,
SharedTokenKind TextHover,
SharedTokenKind TextPressed,
SharedTokenKind BackgroundHover,
SharedTokenKind BackgroundPressed);
}