A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

689 lines
21 KiB

using System;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class FlyoutTests
{
protected bool UseOverlayPopups { get; set; }
[Fact]
public void Opening_Raises_Single_Opening_Event()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Show();
int tracker = 0;
Flyout f = new Flyout();
f.Opening += (s, e) =>
{
tracker++;
};
f.ShowAt(window);
Assert.Equal(1, tracker);
Assert.True(f.IsOpen);
}
}
[Fact]
public void Opening_Raises_Single_Opened_Event()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Show();
int tracker = 0;
Flyout f = new Flyout();
f.Opened += (s, e) =>
{
tracker++;
};
f.ShowAt(window);
Assert.Equal(1, tracker);
}
}
[Fact]
public void Opening_Is_Cancellable()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Show();
int tracker = 0;
Flyout f = new Flyout();
f.Opening += (s, e) =>
{
tracker++;
if (e is CancelEventArgs cancelEventArgs)
{
cancelEventArgs.Cancel = true;
}
};
f.ShowAt(window);
Assert.Equal(1, tracker);
Assert.False(f.IsOpen);
}
}
[Fact]
public void Closing_Raises_Single_Closing_Event()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Show();
int tracker = 0;
Flyout f = new Flyout();
f.Closing += (s, e) =>
{
tracker++;
};
f.ShowAt(window);
f.Hide();
Assert.Equal(1, tracker);
}
}
[Fact]
public void Closing_Raises_Single_Closed_Event()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Show();
int tracker = 0;
Flyout f = new Flyout();
f.Closed += (s, e) =>
{
tracker++;
};
f.ShowAt(window);
f.Hide();
Assert.Equal(1, tracker);
}
}
[Fact]
public void Cancel_Closing_Keeps_Flyout_Open()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Show();
var tracker = 0;
var f = new Flyout();
f.Closing += (s, e) =>
{
tracker++;
e.Cancel = true;
};
f.ShowAt(window);
f.Hide();
Assert.True(f.IsOpen);
Assert.Equal(1, tracker);
}
}
[Fact]
public void Cancel_Light_Dismiss_Closing_Keeps_Flyout_Open()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Width = 100;
window.Height = 100;
var button = new Button
{
Height = 10,
Width = 10,
HorizontalAlignment = Layout.HorizontalAlignment.Left,
VerticalAlignment = Layout.VerticalAlignment.Top
};
window.Content = button;
window.Show();
var tracker = 0;
var f = new Flyout();
f.Content = new Border { Width = 10, Height = 10 };
f.Closing += (s, e) =>
{
tracker++;
e.Cancel = true;
};
f.ShowAt(window);
var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
overlay.RaiseEvent(e);
Assert.Equal(1, tracker);
Assert.True(f.IsOpen);
}
}
[Fact]
public void Light_Dismiss_Closes_Flyout()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Width = 100;
window.Height = 100;
var button = new Button
{
Height = 10,
Width = 10,
HorizontalAlignment = Layout.HorizontalAlignment.Left,
VerticalAlignment = Layout.VerticalAlignment.Top
};
window.Content = button;
window.Show();
var f = new Flyout();
f.Content = new Border { Width = 10, Height = 10 };
f.ShowAt(window);
var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
overlay.RaiseEvent(e);
Assert.False(f.IsOpen);
}
}
[Fact]
public void Light_Dismiss_No_Event_Pass_Through_To_Button()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Width = 100;
window.Height = 100;
bool buttonClicked = false;
var button = new Button()
{
ClickMode = ClickMode.Press
};
button.Click += (s, e) =>
{
buttonClicked = true;
};
window.Content = button;
window.Show();
var f = new Flyout();
f.OverlayDismissEventPassThrough = false; // Focus of test
f.Content = new Border { Width = 10, Height = 10 };
f.ShowAt(window);
var hitTester = new Mock<IHitTester>();
window.HitTesterOverride = hitTester.Object;
hitTester.Setup(x =>
x.HitTestFirst(new Point(90, 90), window, It.IsAny<Func<Visual, bool>>()))
.Returns(button);
var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
overlay.RaiseEvent(e);
Assert.False(f.IsOpen);
Assert.False(buttonClicked); // Button is NOT clicked
}
}
[Fact]
public void Light_Dismiss_Event_Pass_Through_To_Button()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
window.Width = 100;
window.Height = 100;
bool buttonClicked = false;
var button = new Button()
{
ClickMode = ClickMode.Press
};
button.Click += (s, e) =>
{
buttonClicked = true;
};
window.Content = button;
window.Show();
var f = new Flyout();
f.OverlayDismissEventPassThrough = true; // Focus of test
f.Content = new Border { Width = 10, Height = 10 };
f.ShowAt(window);
var hitTester = new Mock<IHitTester>();
window.HitTesterOverride = hitTester.Object;
hitTester.Setup(x =>
x.HitTestFirst(new Point(90, 90), window, It.IsAny<Func<Visual, bool>>()))
.Returns(button);
var e = CreatePointerPressedEventArgs(window, new Point(90, 90));
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
overlay.RaiseEvent(e);
Assert.False(f.IsOpen);
Assert.True(buttonClicked); // Button is clicked
}
}
[Fact]
public void Flyout_Has_Uncancellable_Close_Before_Showing_On_A_Different_Target()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
Button target1 = new Button();
Button target2 = new Button();
window.Content = new StackPanel
{
Children =
{
target1,
target2
}
};
window.Show();
bool closingFired = false;
bool closedFired = false;
Flyout f = new Flyout();
f.Closing += (s, e) =>
{
closingFired = true; //This shouldn't happen
};
f.Closed += (s, e) =>
{
closedFired = true;
};
f.ShowAt(target1);
f.ShowAt(target2);
Assert.False(closingFired);
Assert.True(closedFired);
}
}
[Fact]
public void ShowMode_Standard_Attemps_Focus_Flyout_Content()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
var flyoutTextBox = new TextBox();
var button = new Button
{
Flyout = new Flyout
{
ShowMode = FlyoutShowMode.Standard,
Content = new Panel
{
Children =
{
flyoutTextBox
}
}
}
};
window.Content = button;
window.Show();
button.Focus();
Assert.Same(button, window.FocusManager!.GetFocusedElement());
button.Flyout.ShowAt(button);
Assert.False(button.IsFocused);
Assert.Same(flyoutTextBox, window.FocusManager!.GetFocusedElement());
}
}
[Fact]
public void ShowMode_Transient_Does_Not_Move_Focus_From_Target()
{
using (CreateServicesWithFocus())
{
var window = PreparedWindow();
var flyoutTextBox = new TextBox();
var button = new Button
{
Flyout = new Flyout
{
ShowMode = FlyoutShowMode.Transient,
Content = new Panel
{
Children =
{
flyoutTextBox
}
}
},
Content = "Test"
};
window.Content = button;
window.Show();
button.Focus();
Assert.True(window.FocusManager.GetFocusedElement() == button);
button.Flyout.ShowAt(button);
Assert.True(window.FocusManager.GetFocusedElement() == button);
}
}
[Fact]
public void ContextRequested_Opens_ContextFlyout()
{
using (CreateServicesWithFocus())
{
var flyout = new Flyout();
var target = new Panel
{
ContextFlyout = flyout
};
var window = PreparedWindow(target);
window.Show();
int openedCount = 0;
flyout.Opened += (sender, args) =>
{
openedCount++;
};
target.RaiseEvent(new ContextRequestedEventArgs());
Assert.True(flyout.IsOpen);
Assert.Equal(1, openedCount);
}
}
[Fact]
public void KeyUp_Raised_On_Target_Opens_ContextFlyout()
{
using (CreateServicesWithFocus())
{
var flyout = new Flyout();
var target = new Panel
{
ContextFlyout = flyout
};
var contextRequestedCount = 0;
target.AddHandler(Control.ContextRequestedEvent, (s, a) => contextRequestedCount++, Interactivity.RoutingStrategies.Tunnel);
var window = PreparedWindow(target);
window.Show();
target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
Assert.True(flyout.IsOpen);
Assert.Equal(1, contextRequestedCount);
}
}
[Fact]
public void KeyUp_Raised_On_Target_Closes_Opened_ContextFlyout()
{
using (CreateServicesWithFocus())
{
var flyout = new Flyout();
var target = new Panel
{
ContextFlyout = flyout
};
var window = PreparedWindow(target);
window.Show();
target.RaiseEvent(new ContextRequestedEventArgs());
Assert.True(flyout.IsOpen);
target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
Assert.False(flyout.IsOpen);
}
}
[Fact]
public void KeyUp_Raised_On_Flyout_Closes_Opened_ContextFlyout()
{
using (CreateServicesWithFocus())
{
var flyoutContent = new Button();
var flyout = new Flyout()
{
Content = flyoutContent
};
var target = new Panel
{
ContextFlyout = flyout
};
var window = PreparedWindow(target);
window.Show();
target.RaiseEvent(new ContextRequestedEventArgs());
Assert.True(flyout.IsOpen);
flyoutContent.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyUpEvent, Key = Key.Apps, Source = window });
Assert.False(flyout.IsOpen);
}
}
[Fact]
public void Should_Reset_Popup_Parent_On_Target_Detached()
{
using (CreateServicesWithFocus())
{
var userControl = new UserControl();
var window = PreparedWindow(userControl);
window.Show();
var flyout = new TestFlyout();
flyout.ShowAt(userControl);
var popup = Assert.IsType<Popup>(flyout.Popup);
Assert.NotNull(popup.Parent);
window.Content = null;
Assert.Null(popup.Parent);
}
}
[Fact]
public void Should_Reset_Popup_Parent_On_Target_Attach_Following_Detach()
{
using (CreateServicesWithFocus())
{
var userControl = new UserControl();
var window = PreparedWindow(userControl);
window.Show();
var flyout = new TestFlyout();
flyout.ShowAt(userControl);
var popup = Assert.IsType<Popup>(flyout.Popup);
Assert.NotNull(popup.Parent);
flyout.Hide();
flyout.ShowAt(userControl);
Assert.NotNull(popup.Parent);
}
}
[Fact]
public void ContextFlyout_Can_Be_Set_In_Styles()
{
using (CreateServicesWithFocus())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='TextBlock'>
<Setter Property='ContextFlyout'>
<MenuFlyout>
<MenuItem>Foo</MenuItem>
</MenuFlyout>
</Setter>
</Style>
</Window.Styles>
<StackPanel>
<TextBlock Name='target1'/>
<TextBlock Name='target2'/>
</StackPanel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var target1 = window.Find<TextBlock>("target1");
var target2 = window.Find<TextBlock>("target2");
var mouse = new MouseTestHelper();
Assert.NotNull(target1.ContextFlyout);
Assert.NotNull(target2.ContextFlyout);
Assert.Same(target1.ContextFlyout, target2.ContextFlyout);
window.Show();
var menu = target1.ContextFlyout;
mouse.Click(target1, MouseButton.Right);
Assert.True(menu.IsOpen);
mouse.Click(target2, MouseButton.Right);
Assert.True(menu.IsOpen);
}
}
[Fact]
public void Setting_FlyoutPresenterClasses_Sets_Classes_On_FlyoutPresenter()
{
using (CreateServicesWithFocus())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='FlyoutPresenter.TestClass'>
<Setter Property='Background' Value='Red' />
</Style>
</Window.Styles>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var flyoutPanel = new Panel();
var button = new Button
{
Content = "Test",
Flyout = new Flyout
{
Content = flyoutPanel
}
};
window.Content = button;
window.Show();
(button.Flyout as Flyout).FlyoutPresenterClasses.Add("TestClass");
button.Flyout.ShowAt(button);
var presenter = flyoutPanel.GetVisualAncestors().OfType<FlyoutPresenter>().FirstOrDefault();
Assert.NotNull(presenter);
Assert.True((presenter.Background as ISolidColorBrush).Color == Colors.Red);
}
}
private IDisposable CreateServicesWithFocus()
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
new MockWindowingPlatform(null,
x => UseOverlayPopups ? null : MockWindowingPlatform.CreatePopupMock(x).Object),
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice()));
}
private static Window PreparedWindow(object content = null)
{
var platform = AvaloniaLocator.Current.GetRequiredService<IWindowingPlatform>();
var windowImpl = Mock.Get(platform.CreateWindow());
windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
var w = new Window(windowImpl.Object) { Content = content };
w.ApplyTemplate();
return w;
}
private static PointerPressedEventArgs CreatePointerPressedEventArgs(Window source, Point p)
{
var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
return new PointerPressedEventArgs(
source,
pointer,
source,
p,
0,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
KeyModifiers.None);
}
public class TestFlyout : Flyout
{
public new Popup Popup => base.Popup;
}
}
public class OverlayPopupFlyoutTests : FlyoutTests
{
public OverlayPopupFlyoutTests()
=> UseOverlayPopups = true;
}
}