diff --git a/.ncrunch/AppWithoutLifetime.v3.ncrunchproject b/.ncrunch/AppWithoutLifetime.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/AppWithoutLifetime.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Headless.NUnit.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Headless.XUnit.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 92c924f107..5e770a0170 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -39,14 +39,13 @@ "src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj", "src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj", "src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj", - "src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj", - "src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj", - "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", - "src\\tools\\DevGenerators\\DevGenerators.csproj", - "src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj", "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj", "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj", + "src\\tools\\Avalonia.Analyzers\\Avalonia.Analyzers.csproj", + "src\\tools\\Avalonia.Generators\\Avalonia.Generators.csproj", + "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", + "src\\tools\\DevGenerators\\DevGenerators.csproj", "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj", "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj", "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj", @@ -66,4 +65,4 @@ "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj" ] } -} +} \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 971a6a6718..4d617fd313 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -307,7 +307,7 @@ namespace ControlCatalog.Pages Content: "; - resultText += await ReadTextFromFile(file, 10000); + resultText += await ReadTextFromFile(file, 500); } openedFileContent.Text = resultText; diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index 3a2464e9fd..1faa74c1ce 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -4,7 +4,27 @@ xmlns="https://github.com/avaloniaui" xmlns:viewModels="using:ControlCatalog.ViewModels" x:DataType="viewModels:TabControlPageViewModel"> - + + + + + + - + - + - - - - - - - - + + + + + + + + - @@ -239,16 +238,17 @@ + - - - - + + + + diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml index 1d9815713c..6218fe94ca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml @@ -33,11 +33,12 @@ VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}" HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}" VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}" - Padding="{TemplateBinding Padding}"> + Padding="{TemplateBinding Padding}" + ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}"> + IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/> - - + - - + + @@ -136,7 +136,6 @@ DockPanel.Dock="Top"> - @@ -144,19 +143,45 @@ + - - - - + + - - + + + - - + + + + + - @@ -177,19 +201,20 @@ + - - - - + diff --git a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml index 724adbddd8..4f6fc5f322 100644 --- a/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ScrollViewer.xaml @@ -14,11 +14,12 @@ VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}" HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}" VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}" - Background="{TemplateBinding Background}"> + Background="{TemplateBinding Background}" + ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}"> + IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/> - OpenRead(JSObject item); + + [JSImport("StorageItem.createFromHandle", AvaloniaModule.StorageModuleName)] + public static partial JSObject? StorageItemFromHandle(JSObject handle); [JSImport("StorageItem.getItemsIterator", AvaloniaModule.StorageModuleName)] [return: JSMarshalAs] diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index a28fd4cbde..ef34826238 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -319,13 +319,14 @@ internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder } var kind = storageItem.GetPropertyAsString("kind"); + var item = StorageHelper.StorageItemFromHandle(storageItem)!; switch (kind) { case "directory": - yield return new JSStorageFolder(storageItem); + yield return new JSStorageFolder(item); break; case "file": - yield return new JSStorageFile(storageItem); + yield return new JSStorageFile(item); break; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 05e937fc05..618379757b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -71,6 +71,8 @@ + + diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 834fa84b53..3d952a7ee9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -9,7 +9,7 @@ namespace Avalonia.Markup.Xaml /// public static class AvaloniaXamlLoader { - public interface IRuntimeXamlLoader + internal interface IRuntimeXamlLoader { object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration); } diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 49349b047c..73da7a7fe6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -173,7 +173,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CompiledBindingPath Build() => new CompiledBindingPath(_elements.ToArray(), _rawSource); } - public interface ICompiledBindingPathElement + internal interface ICompiledBindingPathElement { } diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index ec44eeb38f..d041e7d2e6 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -19,7 +19,13 @@ + + + + + + diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 17f44a4b54..1d82725e57 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -76,7 +76,7 @@ namespace Avalonia.Data bool enableDataValidation); /// - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConversionSupressWarningMessage)] public InstancedBinding? Initiate( AvaloniaObject target, AvaloniaProperty? targetProperty, @@ -234,7 +234,7 @@ namespace Avalonia.Data return result; } - protected IObservable GetParentDataContext(AvaloniaObject target) + private IObservable GetParentDataContext(AvaloniaObject target) { // The DataContext is based on the visual parent and not the logical parent: this may // seem counter intuitive considering the fact that property inheritance works on the logical diff --git a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs index 551e017a61..6c70a81298 100644 --- a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs +++ b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs @@ -18,7 +18,7 @@ namespace Avalonia.Markup.Data /// is applied to the property before the properties on the `Binding` object are set. Looking /// at WPF it uses a similar mechanism for bindings that come from XAML. /// - public static class DelayedBinding + internal static class DelayedBinding { private static readonly ConditionalWeakTable> _entries = new(); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs index e858bacb74..c3651594e9 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ArgumentListParser.cs @@ -4,10 +4,7 @@ using Avalonia.Utilities; namespace Avalonia.Markup.Parsers { -#if !BUILDTASK - public -#endif - static class ArgumentListParser + internal static class ArgumentListParser { public static IList ParseArguments(this ref CharacterReader r, char open, char close, char delimiter = ',') { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs index bf11a02fee..74d2ee3153 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/PropertyPathGrammar.cs @@ -7,10 +7,7 @@ using Avalonia.Utilities; namespace Avalonia.Markup.Parsers { -#if !BUILDTASK - public -#endif - class PropertyPathGrammar + internal class PropertyPathGrammar { private enum State { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 557045d469..350b07742f 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -11,7 +11,7 @@ namespace Avalonia.Markup.Parsers /// /// Parses a from text. /// - public class SelectorParser + internal class SelectorParser { private readonly Func _typeResolver; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 7510b48270..39363beae3 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1909,7 +1909,7 @@ namespace Avalonia.Win32.Interop var result = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); if (result >= 0) { - return Marshal.PtrToStringUni((IntPtr)bufferPtr); + return Encoding.Unicode.GetString(bufferPtr, result); } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index 0d0456dbda..42720cbb4c 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs @@ -124,6 +124,19 @@ namespace Avalonia.Base.UnitTests Assert.Equal(2, target.CoreChanges.Count); } + [Fact] + public void CoerceValue_Calls_Coerce_Callback_Only_Once() + { + var target = new Class1 { Foo = 99 }; + + target.MaxFoo = 50; + + target.CoerceFooInvocations.Clear(); + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(new[] { 99 }, target.CoerceFooInvocations); + } + [Fact] public void Coerced_Value_Can_Be_Restored_If_Limit_Changed() { @@ -218,6 +231,18 @@ namespace Avalonia.Base.UnitTests Assert.Equal(1, raised); } + [Fact] + public void Default_Value_Is_Coerced_Only_Once() + { + var target = new Class1(); + + target.MinFoo = 20; + target.CoerceFooInvocations.Clear(); + target.CoerceValue(Class1.FooProperty); + + Assert.Equal(new[] { 11 }, target.CoerceFooInvocations); + } + [Fact] public void ClearValue_Respects_Coerced_Default_Value() { @@ -338,10 +363,12 @@ namespace Avalonia.Base.UnitTests public int MinFoo { get; set; } = 0; public int MaxFoo { get; set; } = 100; + public List CoerceFooInvocations { get; } = new(); public List CoreChanges { get; } = new(); public static int CoerceFoo(AvaloniaObject instance, int value) { + (instance as Class1)?.CoerceFooInvocations.Add(value); return instance is Class1 o ? Math.Clamp(value, o.MinFoo, o.MaxFoo) : Math.Clamp(value, 0, 100); diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs index 34a9947d28..0b3d1a275b 100644 --- a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs +++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -1253,5 +1254,24 @@ namespace Avalonia.Base.UnitTests.Input Assert.Same(expected, result); } + + [Fact] + public void Focuses_First_Child_From_No_Focus() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var button = new Button(); + var root = new TestRoot(button); + var target = new KeyboardNavigationHandler(); + + target.SetOwner(root); + + root.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = Key.Tab, + }); + + Assert.True(button.IsFocused); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs index d5e4693666..02b6592097 100644 --- a/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContentControlTests.cs @@ -24,9 +24,9 @@ namespace Avalonia.Controls.UnitTests target.Content = "Foo"; target.Template = GetTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); - var child = ((Visual)target).VisualChildren.Single(); + var child = target.VisualChildren.Single(); Assert.IsType(child); child = child.VisualChildren.Single(); Assert.IsType(child); @@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests root.Child = target; target.ApplyTemplate(); - ((Control)target.Presenter).ApplyTemplate(); + target.Presenter.ApplyTemplate(); foreach (Control child in target.GetTemplateChildren()) Assert.Equal("foo", child.Tag); @@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = child; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); var contentPresenter = child.GetVisualParent(); Assert.Equal(target, contentPresenter.TemplatedParent); @@ -85,7 +85,7 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = child; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Null(child.TemplatedParent); } @@ -117,7 +117,7 @@ namespace Avalonia.Controls.UnitTests var child = new Control(); target.Content = child; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal(child.Parent, target); Assert.Equal(child.GetLogicalParent(), target); @@ -135,7 +135,7 @@ namespace Avalonia.Controls.UnitTests target.Content = "Foo"; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); var child = target.Presenter.Child; @@ -152,7 +152,7 @@ namespace Avalonia.Controls.UnitTests target.Content = "Foo"; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); var child = target.Presenter.Child; @@ -192,7 +192,7 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = child; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.True(called); } @@ -207,12 +207,12 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = child; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; target.Content = null; - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.True(called); } @@ -228,12 +228,12 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = child1; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; target.Content = child2; - ((Control)target.Presenter).ApplyTemplate(); + target.Presenter.ApplyTemplate(); Assert.True(called); } @@ -245,13 +245,13 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); target.Content = "Foo"; - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal("Foo", ((TextBlock)target.Presenter.Child).Text); target.Content = "Bar"; - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal("Bar", ((TextBlock)target.Presenter.Child).Text); } @@ -263,7 +263,7 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = "Foo"; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal("Foo", target.Presenter.Child.DataContext); } @@ -276,7 +276,7 @@ namespace Avalonia.Controls.UnitTests target.Template = GetTemplate(); target.Content = new TextBlock(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Null(target.Presenter.Child.DataContext); } diff --git a/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs b/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs index 2cf4148fe3..78012a4d25 100644 --- a/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs +++ b/tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls.UnitTests target.Header = "Foo"; target.ApplyTemplate(); - ((ContentPresenter)target.HeaderPresenter).UpdateChild(); + target.HeaderPresenter.UpdateChild(); var child = target.HeaderPresenter.Child; diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 18c1136ccb..6d0b543ede 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -12,6 +12,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml.Templates; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -298,11 +299,11 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(false, item.IsSelected); - RaisePressedEvent(target, item, MouseButton.Left); + RaisePressedEvent(item, MouseButton.Left); Assert.Equal(true, item.IsSelected); - RaisePressedEvent(target, item, MouseButton.Left); + RaisePressedEvent(item, MouseButton.Left); Assert.Equal(false, item.IsSelected); } @@ -328,9 +329,9 @@ namespace Avalonia.Controls.UnitTests } } - private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton) + private void RaisePressedEvent(ListBoxItem item, MouseButton mouseButton) { - _mouse.Click(listBox, item, mouseButton); + _mouse.Click(item, item, mouseButton); } [Fact] @@ -752,6 +753,177 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{10 - x}"), realized); } + [Fact] + public void Arrow_Keys_Should_Move_Selection_Vertical() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray(); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = items, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }), + SelectedIndex = 0, + }; + + Prepare(target); + + RaiseKeyEvent(target, Key.Down); + Assert.Equal(1, target.SelectedIndex); + + RaiseKeyEvent(target, Key.Down); + Assert.Equal(2, target.SelectedIndex); + + RaiseKeyEvent(target, Key.Up); + Assert.Equal(1, target.SelectedIndex); + } + + [Fact] + public void Arrow_Keys_Should_Move_Selection_Horizontal() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray(); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = items, + ItemsPanel = new FuncTemplate(() => new VirtualizingStackPanel + { + Orientation = Orientation.Horizontal + }), + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }), + SelectedIndex = 0, + }; + + Prepare(target); + + RaiseKeyEvent(target, Key.Right); + Assert.Equal(1, target.SelectedIndex); + + RaiseKeyEvent(target, Key.Right); + Assert.Equal(2, target.SelectedIndex); + + RaiseKeyEvent(target, Key.Left); + Assert.Equal(1, target.SelectedIndex); + } + + [Fact] + public void Arrow_Keys_Should_Focus_Selection() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray(); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = items, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Height = 10 }), + SelectedIndex = 0, + }; + + Prepare(target); + + RaiseKeyEvent(target, Key.Down); + Assert.True(target.ContainerFromIndex(1).IsFocused); + + RaiseKeyEvent(target, Key.Down); + Assert.True(target.ContainerFromIndex(2).IsFocused); + + RaiseKeyEvent(target, Key.Up); + Assert.True(target.ContainerFromIndex(1).IsFocused); + } + + [Fact] + public void Down_Key_Selecting_From_No_Selection_And_No_Focus_Selects_From_Start() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = new[] { "Foo", "Bar", "Baz" }, + Width = 100, + Height = 100, + }; + + Prepare(target); + + RaiseKeyEvent(target, Key.Down); + + Assert.Equal(0, target.SelectedIndex); + } + + [Fact] + public void Down_Key_Selecting_From_No_Selection_Selects_From_Focus() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = new[] { "Foo", "Bar", "Baz" }, + Width = 100, + Height = 100, + }; + + Prepare(target); + + target.ContainerFromIndex(1)!.Focus(); + RaiseKeyEvent(target, Key.Down); + + Assert.Equal(2, target.SelectedIndex); + } + + [Fact] + public void Ctrl_Down_Key_Moves_Focus_But_Not_Selection() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = new[] { "Foo", "Bar", "Baz" }, + Width = 100, + Height = 100, + SelectedIndex = 0, + }; + + Prepare(target); + + target.ContainerFromIndex(0)!.Focus(); + RaiseKeyEvent(target, Key.Down, KeyModifiers.Control); + + Assert.Equal(0, target.SelectedIndex); + Assert.True(target.ContainerFromIndex(1).IsFocused); + } + + [Fact] + public void Down_Key_Brings_Unrealized_Selection_Into_View() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var items = Enumerable.Range(0, 100).Select(x => $"Item {x}").ToArray(); + var target = new ListBox + { + Template = ListBoxTemplate(), + ItemsSource = items, + ItemTemplate = new FuncDataTemplate((x, _) => new TextBlock { Width = 20, Height = 10 }), + Width = 100, + Height = 100, + SelectedIndex = 0, + }; + + Prepare(target); + + target.ContainerFromIndex(0)!.Focus(); + target.Scroll.Offset = new Vector(0, 100); + Layout(target); + + var panel = (VirtualizingStackPanel)target.ItemsPanelRoot; + Assert.Equal(10, panel.FirstRealizedIndex); + + RaiseKeyEvent(target, Key.Down); + + Assert.Equal(1, target.SelectedIndex); + Assert.True(target.ContainerFromIndex(1).IsFocused); + Assert.Equal(new Vector(0, 10), target.Scroll.Offset); + } + [Fact] public void WrapSelection_Should_Wrap() { @@ -776,7 +948,7 @@ namespace Avalonia.Controls.UnitTests first.Focus(); - RaisePressedEvent(target, first, MouseButton.Left); + RaisePressedEvent(first, MouseButton.Left); Assert.Equal(true, first.IsSelected); RaiseKeyEvent(target, Key.Up); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs index e77a3529b8..ed19d1dc97 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs @@ -17,76 +17,6 @@ namespace Avalonia.Controls.UnitTests { private MouseTestHelper _helper = new MouseTestHelper(); - [Fact] - public void Focusing_Item_With_Shift_And_Arrow_Key_Should_Add_To_Selection() - { - var target = new ListBox - { - Template = new FuncControlTemplate(CreateListBoxTemplate), - ItemsSource = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Multiple - }; - - ApplyTemplate(target); - - target.SelectedItem = "Foo"; - - target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs - { - NavigationMethod = NavigationMethod.Directional, - KeyModifiers = KeyModifiers.Shift - }); - - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - } - - [Fact] - public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection() - { - var target = new ListBox - { - Template = new FuncControlTemplate(CreateListBoxTemplate), - ItemsSource = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Multiple - }; - - ApplyTemplate(target); - - target.SelectedItem = "Foo"; - - target.Presenter.Panel.Children[1].RaiseEvent(new GotFocusEventArgs - { - NavigationMethod = NavigationMethod.Directional, - KeyModifiers = KeyModifiers.Control - }); - - Assert.Equal(new[] { "Foo" }, target.SelectedItems); - } - - [Fact] - public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection() - { - var target = new ListBox - { - Template = new FuncControlTemplate(CreateListBoxTemplate), - ItemsSource = new[] { "Foo", "Bar", "Baz " }, - SelectionMode = SelectionMode.Multiple - }; - - ApplyTemplate(target); - - target.SelectedItems.Add("Foo"); - target.SelectedItems.Add("Bar"); - - target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs - { - NavigationMethod = NavigationMethod.Directional, - KeyModifiers = KeyModifiers.Control - }); - - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); - } - [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { @@ -549,6 +479,104 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, target.SelectedItems.Count); } } + + [Fact] + public void Shift_Arrow_Key_Selects_Range() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemsSource = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + Width = 100, + Height = 100, + SelectedIndex = 0, + }; + + var root = new TestRoot(target); + root.LayoutManager.ExecuteInitialLayoutPass(); + + RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift); + + Assert.Equal(new[] { "Foo", "Bar", }, target.SelectedItems); + Assert.Equal(new[] { 0, 1 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(1).IsFocused); + + RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift); + + Assert.Equal(new[] { "Foo", "Bar", "Baz", }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(2).IsFocused); + + RaiseKeyEvent(target, Key.Up, KeyModifiers.Shift); + + Assert.Equal(new[] { "Foo", "Bar", }, target.SelectedItems); + Assert.Equal(new[] { 0, 1 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(1).IsFocused); + } + + [Fact] + public void Shift_Down_Key_Selecting_Selects_Range_End_From_Focus() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemsSource = new[] { "Foo", "Bar", "Baz" }, + SelectionMode = SelectionMode.Multiple, + Width = 100, + Height = 100, + SelectedIndex = 0, + }; + + var root = new TestRoot(target); + root.LayoutManager.ExecuteInitialLayoutPass(); + + target.ContainerFromIndex(1)!.Focus(); + RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift); + + Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(2).IsFocused); + } + + [Fact] + public void Shift_Down_Key_Selecting_Selects_Range_End_From_Focus_Moved_With_Ctrl_Key() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + ItemsSource = new[] { "Foo", "Bar", "Baz", "Qux" }, + SelectionMode = SelectionMode.Multiple, + Width = 100, + Height = 100, + SelectedIndex = 0, + }; + + var root = new TestRoot(target); + root.LayoutManager.ExecuteInitialLayoutPass(); + + RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift); + + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(1).IsFocused); + + RaiseKeyEvent(target, Key.Down, KeyModifiers.Control); + + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(2).IsFocused); + + RaiseKeyEvent(target, Key.Down, KeyModifiers.Shift); + + Assert.Equal(new[] { "Foo", "Bar", "Baz", "Qux" }, target.SelectedItems); + Assert.Equal(new[] { 0, 1, 2, 3 }, SelectedContainers(target)); + Assert.True(target.ContainerFromIndex(3).IsFocused); + } + private Control CreateListBoxTemplate(TemplatedControl parent, INameScope scope) { return new ScrollViewer @@ -571,20 +599,14 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope); } - private static void ApplyTemplate(ListBox target) + private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0) { - // Apply the template to the ListBox itself. - target.ApplyTemplate(); - - // Then to its inner ScrollViewer. - var scrollViewer = (ScrollViewer)target.GetVisualChildren().Single(); - scrollViewer.ApplyTemplate(); - - // Then make the ScrollViewer create its child. - ((ContentPresenter)scrollViewer.Presenter).UpdateChild(); - - // Now the ItemsPresenter should be reigstered, so apply its template. - ((Control)target.Presenter).ApplyTemplate(); + target.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + KeyModifiers = inputModifiers, + Key = key + }); } private static IEnumerable SelectedContainers(SelectingItemsControl target) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 5551d1c7df..92ea2df2a9 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -39,45 +39,6 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(-1, target.SelectedIndex); } - [Fact] - public void Focusing_Item_With_Arrow_Key_Should_Select_It() - { - var target = new ListBox - { - Template = new FuncControlTemplate(CreateListBoxTemplate), - ItemsSource = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - - target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs - { - NavigationMethod = NavigationMethod.Directional, - }); - - Assert.Equal(0, target.SelectedIndex); - } - - [Fact] - public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It() - { - var target = new ListBox - { - Template = new FuncControlTemplate(CreateListBoxTemplate), - ItemsSource = new[] { "Foo", "Bar", "Baz " }, - }; - - ApplyTemplate(target); - - target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs - { - NavigationMethod = NavigationMethod.Directional, - KeyModifiers = KeyModifiers.Control - }); - - Assert.Equal(-1, target.SelectedIndex); - } - [Fact] public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It() { @@ -344,7 +305,7 @@ namespace Avalonia.Controls.UnitTests scrollViewer.ApplyTemplate(); // Then make the ScrollViewer create its child. - ((ContentPresenter)scrollViewer.Presenter).UpdateChild(); + scrollViewer.Presenter.UpdateChild(); // Now the ItemsPresenter should be reigstered, so apply its template. target.Presenter.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index 04acd1c36d..dead810dbb 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -1004,7 +1004,7 @@ namespace Avalonia.Controls.UnitTests _layoutManager = layoutManager ?? new LayoutManager(this); } - protected override ILayoutManager CreateLayoutManager() => _layoutManager; + private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } private static Mock CreateMockTopLevelImpl() diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index eaf95b0c8c..506a62525c 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -554,7 +554,7 @@ namespace Avalonia.Controls.UnitTests.Platform var contextMenu = Mock.Of(x => x.MoveSelection(NavigationDirection.Down, true) == true); var e = new KeyEventArgs { Key = Key.Down, Source = contextMenu }; - target.Attach(contextMenu); + target.AttachCore(contextMenu); target.KeyDown(contextMenu, e); Mock.Get(contextMenu).Verify(x => x.MoveSelection(NavigationDirection.Down, true)); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index 9042e84fa1..34a2776bcd 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs @@ -391,7 +391,7 @@ namespace Avalonia.Controls.UnitTests.Presenters templatedParent.ApplyTemplate(); - return ((ContentPresenter)templatedParent.Presenter, templatedParent); + return (templatedParent.Presenter, templatedParent); } private class TestContentControl : ContentControl, IContentPresenterHost diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 51399d1202..888162cfee 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -19,6 +19,7 @@ using Avalonia.Rendering; using System.Threading.Tasks; using Avalonia.Threading; using Avalonia.Interactivity; +using Avalonia.Media; namespace Avalonia.Controls.UnitTests.Primitives { @@ -1076,30 +1077,98 @@ namespace Avalonia.Controls.UnitTests.Primitives } } - private IDisposable CreateServices() + [Fact] + public void GetPosition_On_Control_In_Popup_Called_From_Parent_Should_Return_Valid_Coordinates() { - return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: - new MockWindowingPlatform(null, - x => + // This test only applies when using a PopupRoot host and not an overlay popup. + if (UsePopupHost) + return; + + using (CreateServices()) + { + var popupContent = new Border() { Height = 100, Width = 100, Background = Brushes.Red }; + var popup = new Popup { Child = popupContent, HorizontalOffset = 40, VerticalOffset = 40, Placement = PlacementMode.AnchorAndGravity, + PlacementAnchor = PopupAnchor.TopLeft, PlacementGravity = PopupGravity.BottomRight}; + var popupParent = new Border { Child = popup }; + var root = PreparedWindow(popupParent); + + popup.Open(); + + // Verify that the popup is positioned at 40,40 as descibed by the Horizontal/ + // VerticalOffset: 10,10 becomes 50,50 in screen coordinates. + Assert.Equal(new PixelPoint(50, 50), popupContent.PointToScreen(new Point(10, 10))); + + // The popup parent is positioned at 0,0 in screen coordinates so client and + // screen coordinates are the same. + Assert.Equal(new PixelPoint(10, 10), popupParent.PointToScreen(new Point(10, 10))); + + // The event will be raised on the popup content at 50,50 (90,90 in screen coordinates) + var pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); + var ev = new PointerPressedEventArgs( + popupContent, + pointer, + popupContent.VisualRoot as PopupRoot, + new Point(50 , 50), + 0, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed), + KeyModifiers.None); + + var contentRaised = 0; + var parentRaised = 0; + + // The event is raised on the popup content in popup coordinates. + popupContent.AddHandler(Button.PointerPressedEvent, (s, e) => + { + ++contentRaised; + Assert.Equal(new Point(50, 50), e.GetPosition(popupContent)); + }); + + // The event is raised on the parent in root coordinates (which in this case are + // the same as screen coordinates). + popupParent.AddHandler(Button.PointerPressedEvent, (s, e) => + { + ++parentRaised; + Assert.Equal(new Point(90, 90), e.GetPosition(popupParent)); + }); + + popupContent.RaiseEvent(ev); + + Assert.Equal(1, contentRaised); + Assert.Equal(1, parentRaised); + } + } + + private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null) + { + impl ??= popupParent.PlatformImpl.CreatePopup(); + + var result = new PopupRoot(popupParent, impl) + { + Template = new FuncControlTemplate((parent, scope) => + new ContentPresenter { - if(UsePopupHost) - return null; - return MockWindowingPlatform.CreatePopupMock(x).Object; - }))); + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = parent[!PopupRoot.ContentProperty], + }.RegisterInNameScope(scope)), + }; + + result.ApplyTemplate(); + + return result; + } + + private IDisposable CreateServices() + { + return UnitTestApplication.Start(TestServices.StyledWindow.With( + windowingPlatform: CreateMockWindowingPlatform())); } private IDisposable CreateServicesWithFocus() { - return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform: - new MockWindowingPlatform(null, - x => - { - if (UsePopupHost) - return null; - return MockWindowingPlatform.CreatePopupMock(x).Object; - }), - focusManager: new FocusManager(), - keyboardDevice: () => new KeyboardDevice())); + return UnitTestApplication.Start(TestServices.StyledWindow.With( + windowingPlatform: CreateMockWindowingPlatform(), + focusManager: new FocusManager(), + keyboardDevice: () => new KeyboardDevice())); } @@ -1116,6 +1185,23 @@ namespace Avalonia.Controls.UnitTests.Primitives KeyModifiers.None); } + private MockWindowingPlatform CreateMockWindowingPlatform() + { + return new MockWindowingPlatform(() => + { + var mock = MockWindowingPlatform.CreateWindowMock(); + + mock.Setup(x => x.CreatePopup()).Returns(() => + { + if (UsePopupHost) + return null; + return MockWindowingPlatform.CreatePopupMock(mock.Object).Object; + }); + + return mock.Object; + }, null); + } + private static Window PreparedWindow(object content = null) { var w = new Window { Content = content }; diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index 9a7d51d86d..94e24e484f 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -440,7 +440,7 @@ namespace Avalonia.Controls.UnitTests throw new InvalidOperationException("Could not get the point in root coordinates."); } - private Control CreateTemplate(ScrollViewer control, INameScope scope) + internal static Control CreateTemplate(ScrollViewer control, INameScope scope) { return new Grid { @@ -480,7 +480,7 @@ namespace Avalonia.Controls.UnitTests }; } - private Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope) + private static Control CreateScrollBarTemplate(ScrollBar scrollBar, INameScope scope) { return new Border { diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 398ac3ff43..7721018453 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -272,28 +272,28 @@ namespace Avalonia.Controls.UnitTests ApplyTemplate(target); - ((ContentPresenter)target.ContentPart).UpdateChild(); + target.ContentPart.UpdateChild(); var dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal(items[0], dataContext); target.SelectedIndex = 1; - ((ContentPresenter)target.ContentPart).UpdateChild(); + target.ContentPart.UpdateChild(); dataContext = ((Button)target.ContentPart.Child).DataContext; Assert.Equal(items[1], dataContext); target.SelectedIndex = 2; - ((ContentPresenter)target.ContentPart).UpdateChild(); + target.ContentPart.UpdateChild(); dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Base", dataContext); target.SelectedIndex = 3; - ((ContentPresenter)target.ContentPart).UpdateChild(); + target.ContentPart.UpdateChild(); dataContext = ((TextBlock)target.ContentPart.Child).DataContext; Assert.Equal("Qux", dataContext); target.SelectedIndex = 4; - ((ContentPresenter)target.ContentPart).UpdateChild(); - dataContext = ((Control)target.ContentPart).DataContext; + target.ContentPart.UpdateChild(); + dataContext = target.ContentPart.DataContext; Assert.Equal("Base", dataContext); } @@ -367,7 +367,7 @@ namespace Avalonia.Controls.UnitTests var root = new TestRoot(target); ApplyTemplate(target); - ((ContentPresenter)target.ContentPart).UpdateChild(); + target.ContentPart.UpdateChild(); var content = Assert.IsType(target.ContentPart.Child); Assert.Equal("bar", content.Tag); @@ -375,6 +375,27 @@ namespace Avalonia.Controls.UnitTests Assert.Single(target.GetLogicalChildren(), content); } + [Fact] + public void SelectedContentTemplate_Updates_After_New_ContentTemplate() + { + TabControl target = new TabControl + { + Template = TabControlTemplate(), + ItemsSource = new[] { "Foo" }, + }; + var root = new TestRoot(target); + + ApplyTemplate(target); + ((ContentPresenter)target.ContentPart).UpdateChild(); + + Assert.Equal(null, Assert.IsType(target.ContentPart.Child).Tag); + + target.ContentTemplate = new FuncDataTemplate((x, _) => + new TextBlock { Tag = "bar", Text = x }); + + Assert.Equal("bar", Assert.IsType(target.ContentPart.Child).Tag); + } + [Fact] public void Should_Not_Propagate_DataContext_To_TabItem_Content() { @@ -587,7 +608,7 @@ namespace Avalonia.Controls.UnitTests tabItem.ApplyTemplate(); - ((ContentPresenter)tabItem.Presenter).UpdateChild(); + tabItem.Presenter.UpdateChild(); } target.ContentPart.ApplyTemplate(); diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index ca1986d293..109c233a56 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -884,7 +884,7 @@ namespace Avalonia.Controls.UnitTests Template = CreateTemplate(), Text = "ABC", MaxLines = 1, - AcceptsReturn= true + AcceptsReturn = true }; var impl = CreateMockTopLevelImpl(); @@ -896,8 +896,11 @@ namespace Avalonia.Controls.UnitTests topLevel.ApplyTemplate(); topLevel.LayoutManager.ExecuteInitialLayoutPass(); + target.ApplyTemplate(); target.Measure(Size.Infinity); + var initialHeight = target.DesiredSize.Height; + topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); @@ -905,7 +908,10 @@ namespace Avalonia.Controls.UnitTests RaiseTextEvent(target, Environment.NewLine); - Assert.Equal("ABC", target.Text); + target.InvalidateMeasure(); + target.Measure(Size.Infinity); + + Assert.Equal(initialHeight, target.DesiredSize.Height); } } @@ -1116,7 +1122,11 @@ namespace Avalonia.Controls.UnitTests private IControlTemplate CreateTemplate() { return new FuncControlTemplate((control, scope) => - new TextPresenter + new ScrollViewer + { + Name = "Part_ScrollViewer", + Template = new FuncControlTemplate(ScrollViewerTests.CreateTemplate), + Content = new TextPresenter { Name = "PART_TextPresenter", [!!TextPresenter.TextProperty] = new Binding @@ -1133,7 +1143,8 @@ namespace Avalonia.Controls.UnitTests Priority = BindingPriority.Template, RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), } - }.RegisterInNameScope(scope)); + }.RegisterInNameScope(scope) + }.RegisterInNameScope(scope)); } private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers) @@ -1208,7 +1219,7 @@ namespace Avalonia.Controls.UnitTests _layoutManager = layoutManager ?? new LayoutManager(this); } - protected override ILayoutManager CreateLayoutManager() => _layoutManager; + private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } private static Mock CreateMockTopLevelImpl() diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 0884dd306a..1c7935e167 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -339,7 +339,7 @@ namespace Avalonia.Controls.UnitTests _layoutManager = layoutManager ?? new LayoutManager(this); } - protected override ILayoutManager CreateLayoutManager() => _layoutManager; + private protected override ILayoutManager CreateLayoutManager() => _layoutManager; } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs index c0a5414ee3..fc2325b29e 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs @@ -27,6 +27,15 @@ namespace Avalonia.IntegrationTests.Appium Assert.False(button.Enabled); } + [Fact] + public void EffectivelyDisabledButton() + { + var button = _session.FindElementByAccessibilityId("EffectivelyDisabledButton"); + + Assert.Equal("Effectively Disabled Button", button.Text); + Assert.False(button.Enabled); + } + [Fact] public void BasicButton() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 7ed43efd94..9d82674fb6 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -472,7 +472,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text); } @@ -694,7 +694,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text); } @@ -727,7 +727,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text); } @@ -760,7 +760,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs index 4404564733..ea2407e18d 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -74,7 +74,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); button.ApplyTemplate(); - var presenter = (ContentPresenter)button.Presenter; + var presenter = button.Presenter; Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); @@ -108,7 +108,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); button.ApplyTemplate(); - var presenter = (ContentPresenter)button.Presenter; + var presenter = button.Presenter; Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); @@ -139,7 +139,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); button.ApplyTemplate(); - var presenter = (ContentPresenter)button.Presenter; + var presenter = button.Presenter; Assert.Equal(Dock.Top, DockPanel.GetDock(presenter)); var diagnostic = presenter.GetDiagnostic(DockPanel.DockProperty); @@ -173,7 +173,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); button.ApplyTemplate(); - var presenter = (ContentPresenter)button.Presenter; + var presenter = button.Presenter; Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); @@ -207,7 +207,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); button.ApplyTemplate(); - var presenter = (ContentPresenter)button.Presenter; + var presenter = button.Presenter; Assert.Equal(Brushes.Red, presenter.Background); var diagnostic = presenter.GetDiagnostic(Button.BackgroundProperty); @@ -238,7 +238,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); button.ApplyTemplate(); - var presenter = (ContentPresenter)button.Presenter; + var presenter = button.Presenter; Assert.Equal("Foo", presenter.Content); var diagnostic = presenter.GetDiagnostic(ContentPresenter.ContentProperty); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs index 05e81dc0ba..89416ad674 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs @@ -32,7 +32,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Null(target.Presenter.Child); } @@ -59,7 +59,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.IsType(target.Presenter.Child); } @@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.Equal(typeof(string), template.DataType); Assert.IsType(target.Presenter.Child); @@ -145,7 +145,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); Assert.IsType(target.Presenter.Child); } @@ -175,7 +175,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); var dataTemplate = (CustomDataTemplate)target.ContentTemplate; Assert.Null(dataTemplate.FancyDataType); @@ -217,11 +217,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml window.ApplyTemplate(); target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); var canvas = (Canvas)target.Presenter.Child; Assert.Same(viewModel, target.DataContext); - Assert.Same(viewModel.Child, ((Control)target.Presenter).DataContext); + Assert.Same(viewModel.Child, target.Presenter.DataContext); Assert.Same(viewModel.Child.Child, canvas.DataContext); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs index 6d26de2b29..26fba7ec57 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs @@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.UnitTests public XamlTestBase() { // Ensure necessary assemblies are loaded. - var _ = typeof(TemplateBinding); + var _ = typeof(Binding); GC.KeepAlive(typeof(ItemsRepeater)); if (AvaloniaLocator.Current.GetService() == null) AvaloniaLocator.CurrentMutable.Bind() diff --git a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs index e1ab54f775..ed96c4ef6e 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs @@ -22,7 +22,7 @@ namespace Avalonia.ReactiveUI.UnitTests Content = "Foo" }; target.ApplyTemplate(); - ((ContentPresenter)target.Presenter).UpdateChild(); + target.Presenter.UpdateChild(); var child = ((Visual)target).GetVisualChildren().Single(); Assert.IsType(child); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index aa5d707d0f..1d07e780e6 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -1071,6 +1071,55 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_GetTextBounds_BiDi() + { + var text = "אבגדה 12345 ABCDEF אבגדה"; + + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + var textSource = new SingleBufferTextSource(text, defaultProperties, true); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, + true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0)); + + var bounds = textLine.GetTextBounds(6, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(0, bounds[0].Rectangle.Left); + + bounds = textLine.GetTextBounds(5, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(36.005859374999993, bounds[0].Rectangle.Left); + + bounds = textLine.GetTextBounds(0, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(71.165859375, bounds[0].Rectangle.Right); + + bounds = textLine.GetTextBounds(11, 1); + + Assert.Equal(1, bounds.Count); + + Assert.Equal(71.165859375, bounds[0].Rectangle.Left); + + bounds = textLine.GetTextBounds(0, 25); + + Assert.Equal(5, bounds.Count); + + Assert.Equal(textLine.WidthIncludingTrailingWhitespace, bounds.Last().Rectangle.Right); + } + } + private class FixedRunsTextSource : ITextSource { private readonly IReadOnlyList _textRuns; diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index ca71a97a6e..5451773da4 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -24,7 +24,6 @@ namespace Avalonia.UnitTests public static Mock CreateWindowMock(double initialWidth = 800, double initialHeight = 600) { var windowImpl = new Mock(); - var position = new PixelPoint(); var clientSize = new Size(initialWidth, initialHeight); windowImpl.SetupAllProperties(); @@ -35,7 +34,6 @@ namespace Avalonia.UnitTests windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object); - windowImpl.Setup(x => x.Position).Returns(() => position); windowImpl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null); @@ -51,10 +49,16 @@ namespace Avalonia.UnitTests windowImpl.Setup(x => x.Move(It.IsAny())).Callback(x => { - position = x; + windowImpl.Setup(x => x.Position).Returns(x); windowImpl.Object.PositionChanged?.Invoke(x); }); + windowImpl.Setup(x => x.PointToScreen(It.IsAny())) + .Returns((point) => PixelPoint.FromPoint(point, 1) + windowImpl.Object.Position); + + windowImpl.Setup(x => x.PointToClient(It.IsAny())) + .Returns(point => (point - windowImpl.Object.Position).ToPoint(1)); + windowImpl.Setup(x => x.Resize(It.IsAny(), It.IsAny())) .Callback((x, y) => { @@ -73,9 +77,6 @@ namespace Avalonia.UnitTests windowImpl.Object.Activated?.Invoke(); }); - windowImpl.Setup(x => x.PointToScreen(It.IsAny())) - .Returns((Point p) => PixelPoint.FromPoint(p, 1D) + position); - return windowImpl; } @@ -83,10 +84,12 @@ namespace Avalonia.UnitTests { var popupImpl = new Mock(); var clientSize = new Size(); + var position = new PixelPoint(); var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) => { clientSize = size.Constrain(s_screenSize); + position = pos; popupImpl.Object.PositionChanged?.Invoke(pos); popupImpl.Object.Resized?.Invoke(clientSize, WindowResizeReason.Unspecified); }); @@ -100,6 +103,15 @@ namespace Avalonia.UnitTests popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); popupImpl.Setup(x => x.RenderScaling).Returns(1); popupImpl.Setup(x => x.PopupPositioner).Returns(positioner); + popupImpl.Setup(x => x.Position).Returns(()=>position); + + popupImpl.Setup(x => x.PointToScreen(It.IsAny())) + .Returns((point) => PixelPoint.FromPoint(point, 1) + position); + + popupImpl.Setup(x => x.PointToClient(It.IsAny())) + .Returns(point => (point - position).ToPoint(1)); + + popupImpl.Setup(r => r.TryGetFeature(It.IsAny())).Returns(null); popupImpl.Setup(x => x.Dispose()).Callback(() => diff --git a/tests/Avalonia.UnitTests/TestExtensions.cs b/tests/Avalonia.UnitTests/TestExtensions.cs deleted file mode 100644 index 21a67a8800..0000000000 --- a/tests/Avalonia.UnitTests/TestExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; -using Avalonia.Layout; -using Avalonia.Styling; - -namespace Avalonia.UnitTests -{ - public static class TestExtensions - { - public static void ApplyTemplate(this IContentPresenter presenter) => ((Layoutable)presenter).ApplyTemplate(); - } -} diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 8dabfe2197..80f0cf5406 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -51,8 +51,6 @@ namespace Avalonia.UnitTests public IRenderer Renderer { get; set; } - public IAccessKeyHandler AccessKeyHandler => null; - public IKeyboardNavigationHandler KeyboardNavigationHandler => null; public IFocusManager FocusManager => AvaloniaLocator.Current.GetService();