diff --git a/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs b/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs index 2ab6c26e68..df80931367 100644 --- a/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs +++ b/samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs @@ -21,7 +21,7 @@ namespace BindingDemo.ViewModels } else { - throw new ArgumentOutOfRangeException("Value must be less than 10."); + throw new ArgumentOutOfRangeException(nameof(value), "Value must be less than 10."); } } } diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml index 932f3292ff..8f147638b5 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml @@ -10,7 +10,8 @@ HorizontalAlignment="Center" Spacing="16"> + Margin="16" + Padding="48,48,48,48"> @@ -31,7 +32,24 @@ - + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index dc73bef07a..96e8b49f89 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; +using ControlCatalog.ViewModels; namespace ControlCatalog.Pages { @@ -8,6 +9,7 @@ namespace ControlCatalog.Pages public ContextMenuPage() { this.InitializeComponent(); + DataContext = new ContextMenuPageViewModel(); } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 5a07cae89f..0a77607719 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using ControlCatalog.ViewModels; using ReactiveUI; namespace ControlCatalog.Pages @@ -13,51 +14,7 @@ namespace ControlCatalog.Pages public MenuPage() { this.InitializeComponent(); - var vm = new MenuPageViewModel(); - - vm.MenuItems = new[] - { - new MenuItemViewModel - { - Header = "_File", - Items = new[] - { - new MenuItemViewModel { Header = "_Open...", Command = vm.OpenCommand }, - new MenuItemViewModel { Header = "Save", Command = vm.SaveCommand }, - new MenuItemViewModel { Header = "-" }, - new MenuItemViewModel - { - Header = "Recent", - Items = new[] - { - new MenuItemViewModel - { - Header = "File1.txt", - Command = vm.OpenRecentCommand, - CommandParameter = @"c:\foo\File1.txt" - }, - new MenuItemViewModel - { - Header = "File2.txt", - Command = vm.OpenRecentCommand, - CommandParameter = @"c:\foo\File2.txt" - }, - } - }, - } - }, - new MenuItemViewModel - { - Header = "_Edit", - Items = new[] - { - new MenuItemViewModel { Header = "_Copy" }, - new MenuItemViewModel { Header = "_Paste" }, - } - } - }; - - DataContext = vm; + DataContext = new MenuPageViewModel(); } private void InitializeComponent() @@ -65,51 +22,4 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } } - - public class MenuPageViewModel - { - public MenuPageViewModel() - { - OpenCommand = ReactiveCommand.CreateFromTask(Open); - SaveCommand = ReactiveCommand.Create(Save); - OpenRecentCommand = ReactiveCommand.Create(OpenRecent); - } - - public IReadOnlyList MenuItems { get; set; } - public ReactiveCommand OpenCommand { get; } - public ReactiveCommand SaveCommand { get; } - public ReactiveCommand OpenRecentCommand { get; } - - public async Task Open() - { - var dialog = new OpenFileDialog(); - var result = await dialog.ShowAsync(App.Current.MainWindow); - - if (result != null) - { - foreach (var path in result) - { - System.Diagnostics.Debug.WriteLine($"Opened: {path}"); - } - } - } - - public void Save() - { - System.Diagnostics.Debug.WriteLine("Save"); - } - - public void OpenRecent(string path) - { - System.Diagnostics.Debug.WriteLine($"Open recent: {path}"); - } - } - - public class MenuItemViewModel - { - public string Header { get; set; } - public ICommand Command { get; set; } - public object CommandParameter { get; set; } - public IList Items { get; set; } - } } diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index 625b344b8c..fea55bcb07 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -1,6 +1,14 @@ + + + + + + + + \ No newline at end of file + diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index e7d6df5a93..fd593db991 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -109,6 +109,8 @@ namespace Avalonia.Media /// The geometry. public void DrawGeometry(IBrush brush, Pen pen, Geometry geometry) { + Contract.Requires(geometry != null); + if (brush != null || PenIsVisible(pen)) { PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl); diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs index a26a5341c8..ac0cc1c17d 100644 --- a/src/Avalonia.Visuals/Media/GeometryDrawing.cs +++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs @@ -31,7 +31,10 @@ public override void Draw(DrawingContext context) { - context.DrawGeometry(Brush, Pen, Geometry); + if (Geometry != null) + { + context.DrawGeometry(Brush, Pen, Geometry); + } } public override Rect GetBounds() @@ -41,4 +44,4 @@ return Geometry?.GetRenderBounds(pen) ?? new Rect(); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index 46d264e518..2d01b117d9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -89,9 +89,14 @@ namespace Avalonia.Rendering.SceneGraph /// public override bool HitTest(Point p) { - p *= Transform.Invert(); - return (Brush != null && Geometry.FillContains(p)) || - (Pen != null && Geometry.StrokeContains(Pen, p)); + if (Transform.HasInverse) + { + p *= Transform.Invert(); + return (Brush != null && Geometry.FillContains(p)) || + (Pen != null && Geometry.StrokeContains(Pen, p)); + } + + return false; } } } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index d2b4c97a05..cc7da96ee8 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -552,7 +552,7 @@ namespace Avalonia { if (c == null) { - throw new ArgumentNullException("Cannot add null to VisualChildren."); + throw new ArgumentNullException(nameof(c), "Cannot add null to VisualChildren."); } if (c.VisualParent != null) diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 44d887241c..878689442d 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -84,7 +84,7 @@ namespace Avalonia.Gtk3 public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height) { _widget = widget; - _surface = surface ?? throw new ArgumentNullException(); + _surface = surface ?? throw new ArgumentNullException(nameof(surface)); _factor = factor; _width = width; _height = height; diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 38d207a31d..3c1ef84c54 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs index c30822aacb..be92b6a8d7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.Templates; namespace Avalonia.Markup.Xaml { + using System.Reflection; using Avalonia.Media; /// @@ -41,7 +42,8 @@ namespace Avalonia.Markup.Xaml { typeof(WindowIcon), typeof(IconTypeConverter) }, { typeof(CultureInfo), typeof(CultureInfoConverter) }, { typeof(Uri), typeof(AvaloniaUriTypeConverter) }, - { typeof(FontFamily), typeof(FontFamilyTypeConverter) } + { typeof(FontFamily), typeof(FontFamilyTypeConverter) }, + { typeof(EventInfo), typeof(AvaloniaEventConverter) }, }; internal static Type GetBuiltinTypeConverter(Type type) diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index b99864b050..a1f8bf6cf6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,21 +1,20 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Controls; -using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.PortableXaml; -using Avalonia.Platform; -using Portable.Xaml; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using System.Runtime.Serialization.Json; using System.Text; using System.Xml.Linq; -using System.Linq; +using Avalonia.Controls; +using Avalonia.Markup.Data; +using Avalonia.Markup.Xaml.PortableXaml; +using Avalonia.Platform; +using Portable.Xaml; namespace Avalonia.Markup.Xaml { @@ -24,29 +23,7 @@ namespace Avalonia.Markup.Xaml /// public class AvaloniaXamlLoader { - private readonly AvaloniaXamlSchemaContext _context = GetContext(); - - public bool IsDesignMode - { - get => _context.IsDesignMode; - set => _context.IsDesignMode = value; - } - - private static AvaloniaXamlSchemaContext GetContext() - { - var result = AvaloniaLocator.Current.GetService(); - - if (result == null) - { - result = AvaloniaXamlSchemaContext.Create(); - - AvaloniaLocator.CurrentMutable - .Bind() - .ToConstant(result); - } - - return result; - } + public bool IsDesignMode { get; set; } /// /// Initializes a new instance of the class. @@ -188,7 +165,8 @@ namespace Avalonia.Markup.Xaml LocalAssembly = localAssembly }; - var reader = new XamlXmlReader(stream, _context, readerSettings); + var context = IsDesignMode ? AvaloniaXamlSchemaContext.DesignInstance : AvaloniaXamlSchemaContext.Instance; + var reader = new XamlXmlReader(stream, context, readerSettings); object result = LoadFromReader( reader, diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs new file mode 100644 index 0000000000..665e71bfea --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaEventConverter.cs @@ -0,0 +1,99 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.PortableXaml; +using Portable.Xaml; + +namespace Avalonia.Markup.Xaml.Converters +{ + internal class AvaloniaEventConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + var text = value as string; + if (text != null) + { + var rootObjectProvider = context.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider; + var destinationTypeProvider = context.GetService(typeof(IDestinationTypeProvider)) as IDestinationTypeProvider; + if (rootObjectProvider != null && destinationTypeProvider != null) + { + var target = rootObjectProvider.RootObject; + var eventType = destinationTypeProvider.GetDestinationType(); + var eventParameters = eventType.GetRuntimeMethods().First(r => r.Name == "Invoke").GetParameters(); + // go in reverse to match System.Xaml behaviour + var methods = target.GetType().GetRuntimeMethods().Reverse(); + + // find based on exact match parameter types first + foreach (var method in methods) + { + if (method.Name != text) + continue; + var parameters = method.GetParameters(); + if (eventParameters.Length != parameters.Length) + continue; + if (parameters.Length == 0) + return method.CreateDelegate(eventType, target); + + for (int i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + var eventParam = eventParameters[i]; + if (param.ParameterType != eventParam.ParameterType) + break; + if (i == parameters.Length - 1) + return method.CreateDelegate(eventType, target); + } + } + + // EnhancedXaml: Find method with compatible base class parameters + foreach (var method in methods) + { + if (method.Name != text) + continue; + var parameters = method.GetParameters(); + if (parameters.Length == 0 || eventParameters.Length != parameters.Length) + continue; + + for (int i = 0; i < parameters.Length; i++) + { + var param = parameters[i]; + var eventParam = eventParameters[i]; + if (!param.ParameterType.GetTypeInfo().IsAssignableFrom(eventParam.ParameterType.GetTypeInfo())) + break; + if (i == parameters.Length - 1) + return method.CreateDelegate(eventType, target); + } + } + + var contextProvider = (IXamlSchemaContextProvider)context.GetService(typeof(IXamlSchemaContextProvider)); + var avaloniaContext = (AvaloniaXamlSchemaContext)contextProvider.SchemaContext; + + if (avaloniaContext.IsDesignMode) + { + // We want to ignore missing events in the designer, so if event handler + // wasn't found create an empty delegate. + var lambdaExpression = Expression.Lambda( + eventType, + Expression.Empty(), + eventParameters.Select(x => Expression.Parameter(x.ParameterType))); + return lambdaExpression.Compile(); + } + else + { + throw new XamlObjectWriterException($"Referenced value method {text} in type {target.GetType()} indicated by event {eventType.FullName} was not found"); + } + } + } + return base.ConvertFrom(context, culture, value); + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs index e9f6ba6945..529cbab938 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaMemberAttributeProvider.cs @@ -49,6 +49,18 @@ namespace Avalonia.Markup.Xaml.PortableXaml //Portable.Xaml is not searching for Type Converter result = new TypeConverterAttribute(typeof(SetterValueTypeConverter)); } + else if (attributeType == typeof(TypeConverterAttribute) && _info is EventInfo) + { + // If a type converter for `EventInfo` is registered, then use that to convert + // event handler values. This is used by the designer to override the lookup + // for event handlers with a null handler. + var eventConverter = AvaloniaTypeConverters.GetTypeConverter(typeof(EventInfo)); + + if (eventConverter != null) + { + result = new TypeConverterAttribute(eventConverter); + } + } if (result == null) { @@ -68,4 +80,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml private readonly MemberInfo _info; } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs index 9a493a85c0..326db884a6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -1,21 +1,48 @@ -using Avalonia.Data; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Avalonia.Data; using Avalonia.Markup.Xaml.Context; -using Avalonia.Markup.Data; using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.Styling; using Portable.Xaml; -using Portable.Xaml.ComponentModel; -using System.ComponentModel; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; namespace Avalonia.Markup.Xaml.PortableXaml { internal class AvaloniaXamlSchemaContext : XamlSchemaContext { - public bool IsDesignMode { get; set; } + private static AvaloniaXamlSchemaContext s_instance; + private static AvaloniaXamlSchemaContext s_designInstance; + + public static AvaloniaXamlSchemaContext Instance + { + get + { + if (s_instance == null) + { + s_instance = Create(); + } + + return s_instance; + } + } + + public static AvaloniaXamlSchemaContext DesignInstance + { + get + { + if (s_designInstance == null) + { + s_designInstance = Create(); + s_designInstance.IsDesignMode = true; + } + + return s_designInstance; + } + } + + public bool IsDesignMode { get; private set; } public static AvaloniaXamlSchemaContext Create(IRuntimeTypeProvider typeProvider = null) { return new AvaloniaXamlSchemaContext(typeProvider ?? new AvaloniaRuntimeTypeProvider()); diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 2194223cb7..10cf716912 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs @@ -385,4 +385,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml { } } -} \ No newline at end of file +} diff --git a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs index 369bd8112c..ac974e3bda 100644 --- a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs +++ b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs @@ -85,9 +85,9 @@ namespace Avalonia.Data get { return _ancestorLevel; } set { - if (_ancestorLevel <= 0) + if (value <= 0) { - throw new ArgumentOutOfRangeException("AncestorLevel may not be set to less than 1."); + throw new ArgumentOutOfRangeException(nameof(value), "AncestorLevel may not be set to less than 1."); } _ancestorLevel = value; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 8d1216e1dc..8137ac3f48 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -50,10 +50,10 @@ namespace Avalonia.Markup.Parsers { case SelectorGrammar.OfTypeSyntax ofType: - result = result.OfType(_typeResolver(ofType.Xmlns, ofType.TypeName)); + result = result.OfType(Resolve(ofType.Xmlns, ofType.TypeName)); break; case SelectorGrammar.IsSyntax @is: - result = result.Is(_typeResolver(@is.Xmlns, @is.TypeName)); + result = result.Is(Resolve(@is.Xmlns, @is.TypeName)); break; case SelectorGrammar.ClassSyntax @class: result = result.Class(@class.Class); @@ -113,5 +113,18 @@ namespace Avalonia.Markup.Parsers return result; } + + private Type Resolve(string xmlns, string typeName) + { + var result = _typeResolver(xmlns, typeName); + + if (result == null) + { + var type = string.IsNullOrWhiteSpace(xmlns) ? typeName : xmlns + ':' + typeName; + throw new InvalidOperationException($"Could not resolve type '{type}'"); + } + + return result; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index e17279013d..87b235dce7 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Up_Opens_MenuItem_With_SubMenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var item = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var e = new KeyEventArgs { Key = Key.Up, Source = item }; @@ -27,7 +27,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Down_Opens_MenuItem_With_SubMenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var item = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var e = new KeyEventArgs { Key = Key.Down, Source = item }; @@ -41,7 +41,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Right_Selects_Next_MenuItem() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(x => x.MoveSelection(NavigationDirection.Right, true) == true); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu); var e = new KeyEventArgs { Key = Key.Right, Source = item }; @@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Left_Selects_Previous_MenuItem() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(x => x.MoveSelection(NavigationDirection.Left, true) == true); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu); var e = new KeyEventArgs { Key = Key.Left, Source = item }; @@ -69,7 +69,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Enter_On_Item_With_No_SubMenu_Causes_Click() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu); var e = new KeyEventArgs { Key = Key.Enter, Source = item }; @@ -84,7 +84,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Enter_On_Item_With_SubMenu_Opens_SubMenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var item = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var e = new KeyEventArgs { Key = Key.Enter, Source = item }; @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Escape_Closes_Parent_Menu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu); var e = new KeyEventArgs { Key = Key.Escape, Source = item }; @@ -113,7 +113,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerEnter_Opens_Item_When_Old_Item_Is_Open() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsSubMenuOpen == true && @@ -141,7 +141,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerLeave_Deselects_Item_When_Menu_Not_Open() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object); var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; @@ -156,7 +156,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerLeave_Doesnt_Deselect_Item_When_Menu_Open() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var item = Mock.Of(x => x.IsTopLevel == true && x.Parent == menu.Object); var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item }; @@ -175,7 +175,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Up_Selects_Previous_MenuItem() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var item = Mock.Of(x => x.Parent == parentItem); var e = new KeyEventArgs { Key = Key.Up, Source = item }; @@ -189,7 +189,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Down_Selects_Next_MenuItem() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var item = Mock.Of(x => x.Parent == parentItem); var e = new KeyEventArgs { Key = Key.Down, Source = item }; @@ -203,7 +203,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Left_Closes_Parent_SubMenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var parentItem = Mock.Of(x => x.HasSubMenu == true && x.IsSubMenuOpen == true); var item = Mock.Of(x => x.Parent == parentItem); var e = new KeyEventArgs { Key = Key.Left, Source = item }; @@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Right_With_SubMenu_Items_Opens_SubMenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); var e = new KeyEventArgs { Key = Key.Right, Source = item }; @@ -233,7 +233,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Right_On_TopLevel_Child_Navigates_TopLevel_Selection() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = new Mock(); var parentItem = Mock.Of(x => x.IsSubMenuOpen == true && @@ -263,7 +263,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Enter_On_Item_With_No_SubMenu_Causes_Click() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); @@ -279,7 +279,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Enter_On_Item_With_SubMenu_Opens_SubMenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); var e = new KeyEventArgs { Key = Key.Enter, Source = item }; @@ -294,7 +294,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void Escape_Closes_Parent_MenuItem() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true); var item = Mock.Of(x => x.Parent == parentItem); var e = new KeyEventArgs { Key = Key.Escape, Source = item }; @@ -309,7 +309,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerEnter_Selects_Item() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); @@ -325,7 +325,7 @@ namespace Avalonia.Controls.UnitTests.Platform public void PointerEnter_Opens_Submenu_After_Delay() { var timer = new TestTimer(); - var target = new DefaultMenuInteractionHandler(null, timer.RunOnce); + var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); @@ -344,7 +344,7 @@ namespace Avalonia.Controls.UnitTests.Platform public void PointerEnter_Closes_Sibling_Submenu_After_Delay() { var timer = new TestTimer(); - var target = new DefaultMenuInteractionHandler(null, timer.RunOnce); + var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); @@ -365,7 +365,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerLeave_Deselects_Item() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); @@ -381,7 +381,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerLeave_Doesnt_Deselect_Sibling() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); @@ -398,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerLeave_Doesnt_Deselect_Item_If_Pointer_Over_Submenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true); @@ -413,7 +413,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerReleased_On_Item_With_No_SubMenu_Causes_Click() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem); @@ -430,7 +430,7 @@ namespace Avalonia.Controls.UnitTests.Platform public void Selection_Is_Correct_When_Pointer_Temporarily_Exits_Item_To_Select_SubItem() { var timer = new TestTimer(); - var target = new DefaultMenuInteractionHandler(null, timer.RunOnce); + var target = new DefaultMenuInteractionHandler(false, null, timer.RunOnce); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); @@ -467,7 +467,7 @@ namespace Avalonia.Controls.UnitTests.Platform [Fact] public void PointerPressed_On_Item_With_SubMenu_Causes_Opens_Submenu() { - var target = new DefaultMenuInteractionHandler(); + var target = new DefaultMenuInteractionHandler(false); var menu = Mock.Of(); var parentItem = Mock.Of(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu); var item = Mock.Of(x => x.Parent == parentItem && x.HasSubMenu == true); @@ -481,6 +481,23 @@ namespace Avalonia.Controls.UnitTests.Platform } } + public class ContextMenu + { + [Fact] + public void Down_Selects_Selects_First_MenuItem_When_No_Selection() + { + var target = new DefaultMenuInteractionHandler(true); + var contextMenu = Mock.Of(x => x.MoveSelection(NavigationDirection.Down, true) == true); + var e = new KeyEventArgs { Key = Key.Down, Source = contextMenu }; + + target.Attach(contextMenu); + target.KeyDown(contextMenu, e); + + Mock.Get(contextMenu).Verify(x => x.MoveSelection(NavigationDirection.Down, true)); + Assert.True(e.Handled); + } + } + private class TestTimer { private Action _action; diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs new file mode 100644 index 0000000000..05224c2495 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Shapes/PathTests.cs @@ -0,0 +1,16 @@ +using Avalonia.Controls.Shapes; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Shapes +{ + public class PathTests + { + [Fact] + public void Path_With_Null_Data_Does_Not_Throw_On_Measure() + { + var target = new Path(); + + target.Measure(Size.Infinity); + } + } +} diff --git a/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs b/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs index e5a477cc32..4264559ab6 100644 --- a/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs +++ b/tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; using Avalonia.Remote.Protocol; +using Avalonia.Remote.Protocol.Designer; using Avalonia.Remote.Protocol.Viewport; using Xunit; @@ -109,6 +110,16 @@ namespace Avalonia.DesignerSupport.Tests return Guid.NewGuid().ToString(); if (t == typeof(Guid)) return Guid.NewGuid(); + if (t == typeof(Exception)) + return new Exception("Here"); + if (t == typeof(ExceptionDetails)) + return new ExceptionDetails + { + ExceptionType = "Exception", + LineNumber = 5, + LinePosition = 6, + Message = "Here", + }; throw new Exception($"Doesn't know how to fabricate a random value for {t}, path {pathInfo}"); } diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs index dba4573473..1b1a96a7e2 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs @@ -1,8 +1,6 @@ using System; using Avalonia.Controls; -using Avalonia.Controls.Primitives; using Avalonia.Markup.Parsers; -using Avalonia.Styling; using Xunit; namespace Avalonia.Markup.UnitTests.Parsers @@ -15,5 +13,19 @@ namespace Avalonia.Markup.UnitTests.Parsers var target = new SelectorParser((ns, type) => typeof(TextBlock)); var result = target.Parse("TextBlock[IsPointerOver=True]"); } + + [Fact] + public void Throws_If_OfType_Type_Not_Found() + { + var target = new SelectorParser((ns, type) => null); + Assert.Throws(() => target.Parse("NotFound")); + } + + [Fact] + public void Throws_If_Is_Type_Not_Found() + { + var target = new SelectorParser((ns, type) => null); + Assert.Throws(() => target.Parse(":is(NotFound)")); + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs new file mode 100644 index 0000000000..6ea3015138 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs @@ -0,0 +1,66 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Avalonia.Controls; +using Avalonia.Input; +using Portable.Xaml; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class EventTests + { + [Fact] + public void Event_Is_Attached() + { + var xaml = @"