using AtomUI.Controls.Primitives; using AtomUI.Desktop.Controls.DesignTokens; using AtomUI.Theme.Resources; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Headless; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Threading; using Avalonia.VisualTree; using Shouldly; using Xunit; using AvaloniaWindow = Avalonia.Controls.Window; using AtomUISizeType = AtomUI.SizeType; namespace AtomUI.Desktop.Controls.Tests.Steps; public class StepsPanelVisualTests { static StepsPanelVisualTests() { AvaloniaTestApp.EnsureInitialized(); } [Fact] public void Panel_Variant_API_Is_Explicitly_Panel_Scoped() { var steps = new Desktop.Controls.Steps(); Desktop.Controls.Steps.PanelVariantProperty.Name.ShouldBe(nameof(Desktop.Controls.Steps.PanelVariant)); steps.PanelVariant.ShouldBe(Desktop.Controls.StepsPanelVariant.Filled); typeof(Desktop.Controls.Steps).GetProperty("Variant").ShouldBeNull(); typeof(Desktop.Controls.Steps).GetField("VariantProperty").ShouldBeNull(); } [Fact] public void Panel_Uses_Equal_Width_Items_And_Only_Shows_Arrow_On_NonLast_Items() { var steps = CreateSteps(Desktop.Controls.StepsPanelVariant.Filled, Orientation.Vertical); ShowInWindow(steps, () => { var items = steps.Items.Cast().ToArray(); items.Select(item => item.Bounds.Width).ShouldAllBe(width => width == items[0].Bounds.Width); items.Select(item => item.Bounds.X).ShouldBe([0d, 240d, 480d]); for (var index = 0; index < items.Length; index++) { var item = items[index]; FindControl(item, "PART_Indicator").IsVisible.ShouldBeFalse(); FindControl(item, "Connector").IsVisible.ShouldBeFalse(); FindControl(item, "PanelArrow").IsVisible.ShouldBe(index < items.Length - 1); FindControl(item, "NavigationArrow").IsVisible.ShouldBeFalse(); var wrapper = FindControl(item, "ItemWrapper"); wrapper.Bounds.Size.ShouldBe(item.Bounds.Size); } }); } [Fact] public void Panel_Outlined_Uses_Container_Background_And_Current_Primary_Background() { var steps = CreateSteps(Desktop.Controls.StepsPanelVariant.Outlined, Orientation.Horizontal); ShowInWindow(steps, () => { var items = steps.Items.Cast().ToArray(); var current = items[1]; var expectedBackground = GetThemeResource(SharedTokenKind.ColorBgContainer); var expectedActive = GetThemeResource(SharedTokenKind.ColorPrimaryBg); var expectedOutlinedText = GetThemeResource(StepsTokenKind.PanelProcessTextColor); GetColor(items[0].Background).ShouldBe(GetColor(expectedBackground)); GetColor(current.Background).ShouldBe(GetColor(expectedActive)); current.BorderBrush.ShouldNotBeNull(); GetColor(current.Foreground).ShouldBe(GetColor(expectedOutlinedText)); GetColor(((ContentPresenter)FindControl(current, "SubHeaderPresenter")).Foreground).ShouldBe(GetColor(expectedOutlinedText)); GetColor(((ContentPresenter)FindControl(current, "ContentPresenter")).Foreground).ShouldBe(GetColor(expectedOutlinedText)); var currentArrow = (Desktop.Controls.StepsPanelArrow)FindControl(current, "PanelArrow"); var strokeInset = currentArrow.StrokeThickness / 2; currentArrow.Bounds.Top.ShouldBe(strokeInset); currentArrow.Bounds.Bottom.ShouldBe(current.Bounds.Height - strokeInset); steps.PanelVariant = Desktop.Controls.StepsPanelVariant.Filled; Dispatcher.UIThread.RunJobs(); AvaloniaHeadlessPlatform.ForceRenderTimerTick(1); Dispatcher.UIThread.RunJobs(); GetColor(current.Background).ShouldBe(GetColor(GetThemeResource(StepsTokenKind.ProcessIconBgColor))); var expectedFilledText = GetThemeResource(StepsTokenKind.PanelActiveTextColor); GetColor(current.Foreground).ShouldBe(GetColor(expectedFilledText)); GetColor(((ContentPresenter)FindControl(current, "SubHeaderPresenter")).Foreground).ShouldBe(GetColor(expectedFilledText)); GetColor(((ContentPresenter)FindControl(current, "ContentPresenter")).Foreground).ShouldBe(GetColor(expectedFilledText)); currentArrow.Bounds.Top.ShouldBe(0); currentArrow.Bounds.Bottom.ShouldBe(current.Bounds.Height); }); } [Fact] public void Panel_Outlined_NonCurrent_Error_Uses_Container_Background_And_Error_Border() { var steps = CreateSteps(Desktop.Controls.StepsPanelVariant.Outlined, Orientation.Horizontal); steps.Current = 0; var error = steps.Items.Cast().ElementAt(1); error.Status = Desktop.Controls.StepsStatus.Error; ShowInWindow(steps, () => { GetColor(error.Background).ShouldBe(GetColor(GetThemeResource(SharedTokenKind.ColorBgContainer))); GetColor(error.BorderBrush).ShouldBe(GetColor(GetThemeResource(SharedTokenKind.ColorError))); GetColor(error.Foreground).ShouldBe(GetColor(GetThemeResource(StepsTokenKind.PanelErrorTextColor))); steps.Current = 1; Dispatcher.UIThread.RunJobs(); GetColor(error.Background).ShouldBe(GetColor(GetThemeResource(SharedTokenKind.ColorErrorBg))); GetColor(error.Foreground).ShouldBe(GetColor(GetThemeResource(StepsTokenKind.PanelErrorTextColor))); }); } [Fact] public void Panel_Arrow_Uses_Rtl_Overflow_Direction() { var steps = CreateSteps(Desktop.Controls.StepsPanelVariant.Filled, Orientation.Horizontal); steps.FlowDirection = FlowDirection.RightToLeft; ShowInWindow(steps, () => { var first = steps.Items.Cast().First(); var arrow = FindControl(first, "PanelArrow"); arrow.Bounds.Right.ShouldBeLessThanOrEqualTo(2); arrow.Bounds.Left.ShouldBeLessThan(0); }); } [Fact] public void Panel_Filled_Clips_NonFirst_ItemFrame_And_Reserves_Arrow_Inset_For_Content() { var steps = CreateSteps(Desktop.Controls.StepsPanelVariant.Filled, Orientation.Horizontal); ShowInWindow(steps, () => { var items = steps.Items.Cast().ToArray(); var firstArrow = (Desktop.Controls.StepsPanelArrow)FindControl(items[0], "PanelArrow"); var secondWrapper = (Desktop.Controls.StepsPanelItemFrame)FindControl(items[1], "ItemWrapper"); var secondHeader = FindControl(items[1], "HeaderPresenter"); var secondContent = FindControl(items[1], "ContentPresenter"); firstArrow.Width.ShouldBeGreaterThan(0); secondWrapper.BackgroundSizing.ShouldBe(BackgroundSizing.OuterBorderEdge); secondWrapper.Clip.ShouldNotBeNull(); secondHeader.Bounds.Left.ShouldBeGreaterThanOrEqualTo(firstArrow.Width); secondContent.Bounds.Left.ShouldBeGreaterThanOrEqualTo(firstArrow.Width); }); } private static Desktop.Controls.Steps CreateSteps( Desktop.Controls.StepsPanelVariant panelVariant, Orientation orientation) { var steps = new Desktop.Controls.Steps { Width = 720, Height = 120, Type = Desktop.Controls.StepsType.Panel, PanelVariant = panelVariant, Orientation = orientation, SizeType = AtomUISizeType.Middle, Current = 1 }; steps.Items.Add(new Desktop.Controls.StepsItem { Header = "First", SubHeader = "00:01", Content = "Content" }); steps.Items.Add(new Desktop.Controls.StepsItem { Header = "Second", SubHeader = "00:02", Content = "Content" }); steps.Items.Add(new Desktop.Controls.StepsItem { Header = "Last", SubHeader = "00:03", Content = "Content" }); return steps; } private static Control FindControl(Desktop.Controls.StepsItem item, string name) { return item.GetVisualDescendants().OfType().Single(control => control.Name == name); } private static T GetThemeResource(object key) { var application = Application.Current.ShouldNotBeNull(); application.TryGetResource(key, application.ActualThemeVariant, out var value).ShouldBeTrue(); value.ShouldBeAssignableTo(); return (T)value!; } private static Color GetColor(IBrush? brush) { brush.ShouldNotBeNull(); return ((ISolidColorBrush)brush!).Color; } private static void ShowInWindow(Control content, Action assertion) { var window = new AvaloniaWindow { Width = 900, Height = 360, Content = content }; try { window.Show(); Dispatcher.UIThread.RunJobs(); AvaloniaHeadlessPlatform.ForceRenderTimerTick(1); Dispatcher.UIThread.RunJobs(); assertion(); } finally { window.Close(); } } }