using System.Reflection; using AtomUI.Controls; using AtomUI.Controls.Commons; using AtomUI.Controls.Primitives; using AtomUI.Desktop.Controls.DesignTokens; using AtomUI.Theme; using AtomUI.Theme.Configuration; using AtomUI.Theme.Resources; using AtomUI.Theme.Schema; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Media; using Avalonia.Threading; using Avalonia.VisualTree; using Shouldly; using Xunit; using AtomUILineEdit = AtomUI.Desktop.Controls.LineEdit; using AtomUIEmbeddedTextBox = AtomUI.Desktop.Controls.EmbeddedTextBox; using AtomUISearchEdit = AtomUI.Desktop.Controls.SearchEdit; using AtomUITextArea = AtomUI.Desktop.Controls.TextArea; using AtomUITextBox = AtomUI.Desktop.Controls.TextBox; using AtomUIScrollViewer = AtomUI.Desktop.Controls.ScrollViewer; using AvaloniaWindow = Avalonia.Controls.Window; namespace AtomUI.Desktop.Controls.Tests.Input; public class TextBoxVisualStateTests { static TextBoxVisualStateTests() { AvaloniaTestApp.EnsureInitialized(); } [Fact] public void Disabled_TextBox_Uses_Disabled_Text_Foreground() { var textBox = new AtomUITextBox { Width = 160, Text = "3", IsEnabled = false, IsMotionEnabled = false }; ShowInWindow(textBox, () => { var scrollViewer = textBox.GetVisualDescendants() .OfType() .Single(item => item.Name == "ScrollViewer"); BrushShouldHaveSameColor( scrollViewer.Foreground, GetThemeResource(SharedTokenKind.ColorTextDisabled)); }); } [Fact] public void TextBox_InnerBoxDecorator_Uses_Shared_Input_Frame_Tokens_For_Default_Hover_And_Focus() { var textBox = new AtomUITextBox { Width = 180, IsMotionEnabled = false }; ShowInWindow(textBox, () => { var frame = FindTemplatePart(textBox, "PART_InputControlFrame"); var border = FindTemplatePart(textBox, "InnerBoxDecorator"); var expectedThickness = GetThemeResource(SharedTokenKind.BorderThickness); var defaultBorder = GetThemeResource(SharedTokenKind.ColorBorder); var hoverBorder = GetThemeResource(SharedTokenKind.ColorPrimaryHover); var activeBorder = GetThemeResource(SharedTokenKind.ColorPrimary); var activeShadow = GetThemeResource(SharedTokenKind.InputActiveShadow); textBox.BorderThickness.ShouldBe(expectedThickness); border.BorderThickness.ShouldBe(expectedThickness); BrushShouldHaveSameColor(border.BorderBrush, defaultBorder); frame.IsInnerBoxHover = true; Dispatcher.UIThread.RunJobs(); border.BorderThickness.ShouldBe(expectedThickness); BrushShouldHaveSameColor(border.BorderBrush, hoverBorder); textBox.Focus(); Dispatcher.UIThread.RunJobs(); textBox.IsFocused.ShouldBeTrue(); frame.IsInputFocusWithin.ShouldBeTrue(); border.BorderThickness.ShouldBe(expectedThickness); BrushShouldHaveSameColor(border.BorderBrush, activeBorder); border.BoxShadow.ShouldBe(activeShadow); }); } [Fact] public void TextBox_Input_Frame_Width_Remains_Stable_When_Text_Changes() { var textBox = new AtomUITextBox { Width = 360, PlaceholderText = "AtomUI TextBox", IsMotionEnabled = false }; ShowInWindow(textBox, () => { var frame = FindTemplatePart(textBox, "PART_InputControlFrame"); frame.Bounds.Width.ShouldBe(textBox.Bounds.Width); textBox.Text = "2"; Dispatcher.UIThread.RunJobs(); frame.Bounds.Width.ShouldBe(textBox.Bounds.Width); textBox.Text = "2wewwerwewerwerwe"; Dispatcher.UIThread.RunJobs(); frame.Bounds.Width.ShouldBe(textBox.Bounds.Width); }, window => window.Width = 480); } [Theory] [InlineData(ControlAlgorithmMode.Global, true)] [InlineData(ControlAlgorithmMode.Disabled, false)] public void LineEdit_Hover_Uses_Control_Effective_ColorPrimaryHover( ControlAlgorithmMode algorithm, bool expectDerivedHover) { AssertCompositeInputHoverUsesSharedColorPrimaryHover( new AtomUILineEdit { Width = 180, IsMotionEnabled = false }, LineEditTokens.Identity, algorithm, expectDerivedHover); } [Theory] [InlineData(ControlAlgorithmMode.Global)] [InlineData(ControlAlgorithmMode.Disabled)] public void LineEdit_FocusWithin_Uses_Control_Effective_ColorPrimary(ControlAlgorithmMode algorithm) { AssertCompositeInputFocusWithinUsesControlEffectiveColorPrimary( new AtomUILineEdit { Width = 180, IsMotionEnabled = false }, LineEditTokens.Identity, algorithm); } [Theory] [InlineData(ControlAlgorithmMode.Global, true)] [InlineData(ControlAlgorithmMode.Disabled, false)] public void SearchEdit_Hover_Uses_Control_Effective_ColorPrimaryHover( ControlAlgorithmMode algorithm, bool expectDerivedHover) { AssertCompositeInputHoverUsesSharedColorPrimaryHover( new AtomUISearchEdit { Width = 180, IsMotionEnabled = false }, SearchEditTokens.Identity, algorithm, expectDerivedHover); } [Theory] [InlineData(ControlAlgorithmMode.Global)] [InlineData(ControlAlgorithmMode.Disabled)] public void SearchEdit_FocusWithin_Uses_Control_Effective_ColorPrimary(ControlAlgorithmMode algorithm) { AssertCompositeInputFocusWithinUsesControlEffectiveColorPrimary( new AtomUISearchEdit { Width = 180, IsMotionEnabled = false }, SearchEditTokens.Identity, algorithm); } [Theory] [InlineData(ControlAlgorithmMode.Global, true)] [InlineData(ControlAlgorithmMode.Disabled, false)] public void TextArea_Hover_Uses_Control_Effective_ColorPrimaryHover( ControlAlgorithmMode algorithm, bool expectDerivedHover) { AssertCompositeInputHoverUsesSharedColorPrimaryHover( new AtomUITextArea { Width = 180, Height = 80, IsMotionEnabled = false }, TextAreaTokens.Identity, algorithm, expectDerivedHover); } [Theory] [InlineData(ControlAlgorithmMode.Global)] [InlineData(ControlAlgorithmMode.Disabled)] public void TextArea_FocusWithin_Uses_Control_Effective_ColorPrimary(ControlAlgorithmMode algorithm) { AssertCompositeInputFocusWithinUsesControlEffectiveColorPrimary( new AtomUITextArea { Width = 180, Height = 80, IsMotionEnabled = false }, TextAreaTokens.Identity, algorithm); } [Fact] public void TextBox_Uses_Customizable_SizeType_Contract_And_Control_Shared_Token_Resources() { typeof(ICustomizableSizeTypeAware).IsAssignableFrom(typeof(AtomUITextBox)).ShouldBeTrue(); var property = typeof(AtomUITextBox).GetProperty( "SizeType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); property.ShouldNotBeNull(); property!.PropertyType.ShouldBe(typeof(CustomizableSizeType)); var field = typeof(AtomUITextBox).GetField( "SizeTypeProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); field.ShouldNotBeNull(); field!.FieldType.ShouldBe(typeof(StyledProperty)); TextBoxTokens.Identity.Id.ShouldBe(nameof(AtomUI.Desktop.Controls.TextBox)); var source = ReadRepoFile("src/AtomUI.Desktop.Controls/Input/Themes/TextBoxTheme.axaml"); source.ShouldContain("SharedTokenResource FontSize"); source.ShouldNotContain("ControlTokenScope.Identity"); source.ShouldNotContain("TokenSharedTokenResource"); } [Fact] public void TextBox_Uses_Custom_SizeType_For_Template_Owned_Padding_Without_Extra_State() { var property = typeof(AtomUITextBox).GetProperty( "IsCustomPadding", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); property.ShouldBeNull(); var field = typeof(AtomUITextBox).GetField( "IsCustomPaddingProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy); field.ShouldBeNull(); } [Fact] public void EmbeddedTextBox_Is_Internal_TextBox_For_Template_Owned_Input_Chrome() { typeof(AtomUITextBox).IsAssignableFrom(typeof(AtomUIEmbeddedTextBox)).ShouldBeTrue(); typeof(ICustomizableSizeTypeAware).IsAssignableFrom(typeof(AtomUIEmbeddedTextBox)).ShouldBeTrue(); var embeddedTextBox = new AtomUIEmbeddedTextBox { Width = 160, Text = "embedded", IsEnabled = false }; ShowInWindow(embeddedTextBox, () => { embeddedTextBox.SizeType.ShouldBe(CustomizableSizeType.Custom); embeddedTextBox.Padding.ShouldBe(new Thickness(0)); embeddedTextBox.BorderThickness.ShouldBe(new Thickness(0)); embeddedTextBox.IsCustomFontSize.ShouldBeTrue(); embeddedTextBox.IsAllowClear.ShouldBeFalse(); embeddedTextBox.IsEnableRevealButton.ShouldBeFalse(); var border = FindTemplatePart(embeddedTextBox, "InnerBoxDecorator"); border.BorderThickness.ShouldBe(new Thickness(0)); BrushShouldHaveSameColor(border.Background, Brushes.Transparent); BrushShouldHaveSameColor(border.BorderBrush, Brushes.Transparent); }); } [Fact] public void EmbeddedTextBox_Theme_Suppresses_Own_Disabled_Chrome() { var source = ReadRepoFile("src/AtomUI.Desktop.Controls/Input/Themes/EmbeddedTextBoxTheme.axaml"); source.ShouldContain("TargetType=\"atom:EmbeddedTextBox\""); source.ShouldContain(""); source.ShouldContain(""); source.ShouldContain(""); source.ShouldContain(""); source.ShouldContain("