using Shouldly; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Input; using Avalonia.Threading; using Avalonia.VisualTree; using Xunit; namespace AtomUI.Desktop.Controls.Tests.Dialog; public class DialogSurfaceTests { static DialogSurfaceTests() { AvaloniaTestApp.EnsureInitialized(); } [Fact] public void Content_Binding_Preserves_Data_Objects_And_Clears_On_Dispose() { var initialContent = new object(); var dialog = new AtomUI.Desktop.Controls.Dialog { Content = initialContent }; var surface = new DialogSurface(dialog); surface.Content.ShouldBeSameAs(initialContent); dialog.Content = "updated content"; surface.Content.ShouldBe("updated content"); surface.Dispose(); dialog.Content = "after dispose"; surface.Content.ShouldBeNull(); } [Fact] public void Visual_State_And_Effective_Footer_Follow_The_Dialog() { var dialog = new AtomUI.Desktop.Controls.Dialog { Title = "Initial", IsFooterVisible = true, StandardButtons = DialogStandardButton.NoButton, IsLoading = false, IsConfirmLoading = false, IsClosable = true, IsMaximizable = false, IsMotionEnabled = true }; var surface = new DialogSurface(dialog); surface.Title.ShouldBe("Initial"); surface.IsEffectiveFooterVisible.ShouldBeFalse(); dialog.CustomButtons.Add(new DialogButton { Content = "Custom" }); surface.IsEffectiveFooterVisible.ShouldBeTrue(); dialog.IsLoading = true; surface.IsEffectiveFooterVisible.ShouldBeFalse(); dialog.CustomButtons.Clear(); dialog.IsLoading = false; dialog.StandardButtons = DialogStandardButton.Ok; dialog.Title = "Updated"; dialog.IsConfirmLoading = true; surface.Title.ShouldBe("Updated"); surface.IsConfirmLoading.ShouldBeTrue(); surface.StandardButtons.ShouldBe(DialogStandardButton.Ok); surface.IsEffectiveFooterVisible.ShouldBeTrue(); surface.Dispose(); } [Fact] public void Custom_Buttons_Are_Synchronized_Through_The_Surface_Button_Box() { var first = new DialogButton { Content = "First" }; var second = new DialogButton { Content = "Second" }; var replacement = new DialogButton { Content = "Replacement" }; var dialog = new AtomUI.Desktop.Controls.Dialog(); dialog.CustomButtons.Add(first); dialog.CustomButtons.Add(second); var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); var buttonBox = surface.ButtonBox.ShouldNotBeNull(); buttonBox.CustomButtons.ShouldBe(new[] { first, second }); dialog.CustomButtons[0] = replacement; dialog.CustomButtons.Move(1, 0); buttonBox.CustomButtons.ShouldBe(new[] { second, replacement }); dialog.CustomButtons.Clear(); buttonBox.CustomButtons.ShouldBeEmpty(); surface.Dispose(); surface.ButtonBox.ShouldBeNull(); } finally { window.Close(); } } [Fact] public void Assigning_ButtonsConfigure_While_Open_Configures_The_Current_Sequence() { var dialog = new AtomUI.Desktop.Controls.Dialog { StandardButtons = DialogStandardButton.Ok | DialogStandardButton.Cancel }; var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; IReadOnlyList? configuredButtons = null; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); dialog.ButtonsConfigure = buttons => configuredButtons = buttons; configuredButtons.ShouldNotBeNull(); configuredButtons.Select(button => button.StandardButtonType) .ShouldBe(new DialogStandardButton?[] { DialogStandardButton.Ok, DialogStandardButton.Cancel }, ignoreOrder: true); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Button_Click_Raises_Dialog_Event_Before_Requesting_Close() { var button = new DialogButton { Content = "Accept", Role = DialogButtonRole.AcceptRole }; var dialog = new AtomUI.Desktop.Controls.Dialog(); dialog.CustomButtons.Add(button); var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; var buttonClickedCount = 0; DialogSurfaceCloseRequestedEventArgs? closeRequest = null; dialog.ButtonClicked += (_, _) => buttonClickedCount++; surface.CloseRequested += (_, e) => closeRequest = e; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); button.RaiseEvent(new RoutedEventArgs(AtomUI.Desktop.Controls.Button.ClickEvent)); buttonClickedCount.ShouldBe(1); closeRequest.ShouldNotBeNull(); closeRequest.Reason.ShouldBe(DialogCloseReason.Accepted); closeRequest.Result.ShouldBe(DialogCode.Accepted); closeRequest.SourceButton.ShouldBeSameAs(button); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Handled_Button_Click_Does_Not_Request_Close() { var button = new DialogButton { Content = "Accept", Role = DialogButtonRole.AcceptRole }; var dialog = new AtomUI.Desktop.Controls.Dialog(); dialog.CustomButtons.Add(button); var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; var closeRequestCount = 0; dialog.ButtonClicked += (_, e) => e.Handled = true; surface.CloseRequested += (_, _) => closeRequestCount++; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); button.RaiseEvent(new RoutedEventArgs(AtomUI.Desktop.Controls.Button.ClickEvent)); closeRequestCount.ShouldBe(0); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Escape_Invokes_The_Real_Default_Escape_Button_Once() { var dialog = new AtomUI.Desktop.Controls.Dialog { StandardButtons = DialogStandardButton.Ok | DialogStandardButton.Cancel }; var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; var buttonClickedCount = 0; DialogSurfaceCloseRequestedEventArgs? closeRequest = null; dialog.ButtonClicked += (_, _) => buttonClickedCount++; surface.CloseRequested += (_, e) => closeRequest = e; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); surface.TryInvokeStandardButton(Key.Escape).ShouldBeTrue(); buttonClickedCount.ShouldBe(1); closeRequest.ShouldNotBeNull(); closeRequest.Result.ShouldBe(DialogCode.Rejected); closeRequest.SourceButton.StandardButtonType.ShouldBe(DialogStandardButton.Cancel); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Close_Caption_Raises_A_Host_Close_Request() { var dialog = new AtomUI.Desktop.Controls.Dialog(); var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; var closeRequestCount = 0; surface.HostCloseRequested += (_, _) => closeRequestCount++; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); var header = surface.Header.ShouldNotBeNull(); var closeButton = header.GetVisualDescendants() .OfType() .Single(button => button.Name == "PART_CloseButton"); closeButton.RaiseEvent(new RoutedEventArgs(AtomUI.Desktop.Controls.Button.ClickEvent)); closeRequestCount.ShouldBe(1); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Resizable_Surface_Owns_One_Template_Resizer() { var dialog = new AtomUI.Desktop.Controls.Dialog { IsResizable = true }; var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); var resizer = surface.Resizer.ShouldNotBeNull(); resizer.IsVisible.ShouldBeTrue(); dialog.IsResizable = false; resizer.IsVisible.ShouldBeFalse(); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Structural_Minimum_Combines_Header_Content_Viewport_And_Visible_Footer() { var dialog = new AtomUI.Desktop.Controls.Dialog { Title = "Structural minimum", StandardButtons = DialogStandardButton.Ok | DialogStandardButton.Cancel }; var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); var withFooter = surface.MeasureStructuralMinimum(); withFooter.Width.ShouldBeGreaterThanOrEqualTo(surface.ContentViewportMinWidth); withFooter.Height.ShouldBeGreaterThan(surface.ContentViewportMinHeight); dialog.IsFooterVisible = false; Dispatcher.UIThread.RunJobs(); var withoutFooter = surface.MeasureStructuralMinimum(); withoutFooter.Height.ShouldBeLessThan(withFooter.Height); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Structural_Minimum_Does_Not_Include_Arbitrary_Content_DesiredSize() { var content = new Border { Width = 900, Height = 700 }; var dialog = new AtomUI.Desktop.Controls.Dialog { Content = content, IsFooterVisible = false }; var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); var minimum = surface.MeasureStructuralMinimum(); minimum.Width.ShouldBeLessThan(content.Width); minimum.Height.ShouldBeLessThan(content.Height); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Structural_Minimum_Tracks_Effective_Button_Content() { var customButton = new DialogButton { Content = "OK" }; var dialog = new AtomUI.Desktop.Controls.Dialog(); dialog.CustomButtons.Add(customButton); var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); var initialMinimum = surface.MeasureStructuralMinimum(); customButton.Content = new string('W', 80); Dispatcher.UIThread.RunJobs(); var updatedMinimum = surface.MeasureStructuralMinimum(); updatedMinimum.Width.ShouldBeGreaterThan(initialMinimum.Width); } finally { surface.Dispose(); window.Close(); } } [Fact] public void Structural_Minimum_Change_Is_Published_When_Template_Spacing_Changes() { var dialog = new AtomUI.Desktop.Controls.Dialog { StandardButtons = DialogStandardButton.Ok | DialogStandardButton.Cancel }; var surface = new DialogSurface(dialog); var window = new AtomUI.Desktop.Controls.Window { Content = surface }; try { window.Show(); Dispatcher.UIThread.RunJobs(); surface.ApplyTemplate(); Dispatcher.UIThread.RunJobs(); var footerFrame = surface.GetVisualDescendants() .OfType() .Single(border => border.Name == "FooterFrame"); var initialMinimum = surface.MeasureStructuralMinimum(); var changeCount = 0; surface.StructuralMinimumChanged += (_, _) => changeCount++; footerFrame.Padding = new Thickness( footerFrame.Padding.Left, footerFrame.Padding.Top + 20, footerFrame.Padding.Right, footerFrame.Padding.Bottom + 20); Dispatcher.UIThread.RunJobs(); surface.MeasureStructuralMinimum().Height.ShouldBeGreaterThan(initialMinimum.Height); changeCount.ShouldBeGreaterThan(0); } finally { surface.Dispose(); window.Close(); } } }