committed by
GitHub
78 changed files with 1986 additions and 486 deletions
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="Moq" Version="4.7.1" /> |
<PackageReference Include="Moq" Version="4.7.25" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,15 @@ |
|||||
|
// 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; |
||||
|
|
||||
|
namespace Avalonia.Input |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Designates a control as handling its own keyboard navigation.
|
||||
|
/// </summary>
|
||||
|
public interface ICustomKeyboardNavigation |
||||
|
{ |
||||
|
(bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.UnitTests; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
|
||||
|
namespace Avalonia.Benchmarks.Layout |
||||
|
{ |
||||
|
[MemoryDiagnoser] |
||||
|
public class Measure : IDisposable |
||||
|
{ |
||||
|
private IDisposable _app; |
||||
|
private TestRoot root; |
||||
|
private List<Control> controls = new List<Control>(); |
||||
|
|
||||
|
public Measure() |
||||
|
{ |
||||
|
_app = UnitTestApplication.Start(TestServices.RealLayoutManager); |
||||
|
|
||||
|
var panel = new StackPanel(); |
||||
|
root = new TestRoot { Child = panel }; |
||||
|
controls.Add(panel); |
||||
|
CreateChildren(panel, 3, 5); |
||||
|
LayoutManager.Instance.ExecuteInitialLayoutPass(root); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_app.Dispose(); |
||||
|
} |
||||
|
|
||||
|
[Benchmark] |
||||
|
public void Remeasure_Half() |
||||
|
{ |
||||
|
var random = new Random(1); |
||||
|
|
||||
|
foreach (var control in controls) |
||||
|
{ |
||||
|
if (random.Next(2) == 0) |
||||
|
{ |
||||
|
control.InvalidateMeasure(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
LayoutManager.Instance.ExecuteLayoutPass(); |
||||
|
} |
||||
|
|
||||
|
private void CreateChildren(IPanel parent, int childCount, int iterations) |
||||
|
{ |
||||
|
for (var i = 0; i < childCount; ++i) |
||||
|
{ |
||||
|
var control = new StackPanel(); |
||||
|
parent.Children.Add(control); |
||||
|
|
||||
|
if (iterations > 0) |
||||
|
{ |
||||
|
CreateChildren(control, childCount, iterations - 1); |
||||
|
} |
||||
|
|
||||
|
controls.Add(control); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,291 @@ |
|||||
|
// 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.Linq; |
||||
|
using Avalonia.Controls.Presenters; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Avalonia.VisualTree; |
||||
|
using Moq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests.Presenters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Tests for ContentControls that are hosted in a control template.
|
||||
|
/// </summary>
|
||||
|
public class ContentPresenterTests_InTemplate |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Register_With_Host_When_TemplatedParent_Set() |
||||
|
{ |
||||
|
var host = new Mock<IContentPresenterHost>(); |
||||
|
var target = new ContentPresenter(); |
||||
|
|
||||
|
target.SetValue(Control.TemplatedParentProperty, host.Object); |
||||
|
|
||||
|
host.Verify(x => x.RegisterContentPresenter(target)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_Control_Should_Set_Child() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
|
||||
|
Assert.Equal(child, target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_Control_Should_Update_Logical_Tree() |
||||
|
{ |
||||
|
var (target, parent) = CreateTarget(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
|
||||
|
Assert.Equal(parent, child.GetLogicalParent()); |
||||
|
Assert.Equal(new[] { child }, parent.GetLogicalChildren()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_Control_Should_Update_Visual_Tree() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
|
||||
|
Assert.Equal(target, child.GetVisualParent()); |
||||
|
Assert.Equal(new[] { child }, target.GetVisualChildren()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_String_Should_Create_TextBlock() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
|
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
Assert.Equal("Foo", ((TextBlock)target.Child).Text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_String_Should_Update_Logical_Tree() |
||||
|
{ |
||||
|
var (target, parent) = CreateTarget(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
|
||||
|
var child = target.Child; |
||||
|
Assert.Equal(parent, child.GetLogicalParent()); |
||||
|
Assert.Equal(new[] { child }, parent.GetLogicalChildren()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_String_Should_Update_Visual_Tree() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
|
||||
|
var child = target.Child; |
||||
|
Assert.Equal(target, child.GetVisualParent()); |
||||
|
Assert.Equal(new[] { child }, target.GetVisualChildren()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_Control_Content_Should_Update_Logical_Tree() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
target.Content = null; |
||||
|
|
||||
|
Assert.Equal(null, child.GetLogicalParent()); |
||||
|
Assert.Empty(target.GetLogicalChildren()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_Control_Content_Should_Update_Visual_Tree() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
target.Content = null; |
||||
|
|
||||
|
Assert.Equal(null, child.GetVisualParent()); |
||||
|
Assert.Empty(target.GetVisualChildren()); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Control_Content_Should_Not_Be_NameScope() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.Content = new TextBlock(); |
||||
|
|
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
Assert.Null(NameScope.GetNameScope((Control)target.Child)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void DataTemplate_Created_Control_Should_Be_NameScope() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
|
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
Assert.NotNull(NameScope.GetNameScope((Control)target.Child)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_Control_To_Content_Should_Not_Set_DataContext() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
target.Content = new Border(); |
||||
|
|
||||
|
Assert.False(target.IsSet(Control.DataContextProperty)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_NonControl_To_Content_Should_Set_DataContext_On_UpdateChild() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
target.Content = "foo"; |
||||
|
|
||||
|
Assert.Equal("foo", target.DataContext); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Use_ContentTemplate_If_Specified() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()); |
||||
|
target.Content = "Foo"; |
||||
|
|
||||
|
Assert.IsType<Canvas>(target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Update_If_ContentTemplate_Changed() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
|
||||
|
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()); |
||||
|
Assert.IsType<Canvas>(target.Child); |
||||
|
|
||||
|
target.ContentTemplate = null; |
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Assigning_Control_To_Content_After_NonControl_Should_Clear_DataContext() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.Content = "foo"; |
||||
|
|
||||
|
Assert.True(target.IsSet(Control.DataContextProperty)); |
||||
|
|
||||
|
target.Content = new Border(); |
||||
|
|
||||
|
Assert.False(target.IsSet(Control.DataContextProperty)); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Recycles_DataTemplate() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true)); |
||||
|
|
||||
|
target.Content = "foo"; |
||||
|
|
||||
|
var control = target.Child; |
||||
|
Assert.IsType<Border>(control); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Assert.Same(control, target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Detects_DataTemplate_Doesnt_Match_And_Doesnt_Recycle() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "foo", _ => new Border(), true)); |
||||
|
|
||||
|
target.Content = "foo"; |
||||
|
|
||||
|
var control = target.Child; |
||||
|
Assert.IsType<Border>(control); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Detects_DataTemplate_Doesnt_Support_Recycling() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), false)); |
||||
|
|
||||
|
target.Content = "foo"; |
||||
|
|
||||
|
var control = target.Child; |
||||
|
Assert.IsType<Border>(control); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Assert.NotSame(control, target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Reevaluates_DataTemplates_When_Recycling() |
||||
|
{ |
||||
|
var (target, _) = CreateTarget(); |
||||
|
|
||||
|
target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "bar", _ => new Canvas(), true)); |
||||
|
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true)); |
||||
|
|
||||
|
target.Content = "foo"; |
||||
|
|
||||
|
var control = target.Child; |
||||
|
Assert.IsType<Border>(control); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Assert.IsType<Canvas>(target.Child); |
||||
|
} |
||||
|
|
||||
|
(ContentPresenter presenter, ContentControl templatedParent) CreateTarget() |
||||
|
{ |
||||
|
var templatedParent = new ContentControl |
||||
|
{ |
||||
|
Template = new FuncControlTemplate<ContentControl>(x => |
||||
|
new ContentPresenter |
||||
|
{ |
||||
|
Name = "PART_ContentPresenter", |
||||
|
}), |
||||
|
}; |
||||
|
var root = new TestRoot { Child = templatedParent }; |
||||
|
|
||||
|
templatedParent.ApplyTemplate(); |
||||
|
|
||||
|
return ((ContentPresenter)templatedParent.Presenter, templatedParent); |
||||
|
} |
||||
|
|
||||
|
private class TestContentControl : ContentControl |
||||
|
{ |
||||
|
public IControl Child { get; set; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,102 @@ |
|||||
|
// 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.Presenters; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests.Presenters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Tests for ContentControls that are not attached to a logical tree.
|
||||
|
/// </summary>
|
||||
|
public class ContentPresenterTests_Unrooted |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Setting_Content_To_Control_Should_Not_Set_Child_Unless_UpdateChild_Called() |
||||
|
{ |
||||
|
var target = new ContentPresenter(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
Assert.Null(target.Child); |
||||
|
|
||||
|
target.ApplyTemplate(); |
||||
|
Assert.Null(target.Child); |
||||
|
|
||||
|
target.UpdateChild(); |
||||
|
Assert.Equal(child, target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Setting_Content_To_String_Should_Not_Create_TextBlock_Unless_UpdateChild_Called() |
||||
|
{ |
||||
|
var target = new ContentPresenter(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
Assert.Null(target.Child); |
||||
|
|
||||
|
target.ApplyTemplate(); |
||||
|
Assert.Null(target.Child); |
||||
|
|
||||
|
target.UpdateChild(); |
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
Assert.Equal("Foo", ((TextBlock)target.Child).Text); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_Control_Content_Should_Remove_Child_Immediately() |
||||
|
{ |
||||
|
var target = new ContentPresenter(); |
||||
|
var child = new Border(); |
||||
|
|
||||
|
target.Content = child; |
||||
|
target.UpdateChild(); |
||||
|
Assert.Equal(child, target.Child); |
||||
|
|
||||
|
target.Content = null; |
||||
|
Assert.Null(target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Clearing_String_Content_Should_Remove_Child_Immediately() |
||||
|
{ |
||||
|
var target = new ContentPresenter(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
target.UpdateChild(); |
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
|
||||
|
target.Content = null; |
||||
|
Assert.Null(target.Child); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Adding_To_Logical_Tree_Should_Reevaluate_DataTemplates() |
||||
|
{ |
||||
|
var root = new TestRoot(); |
||||
|
var target = new ContentPresenter(); |
||||
|
|
||||
|
target.Content = "Foo"; |
||||
|
Assert.Null(target.Child); |
||||
|
|
||||
|
root.Child = target; |
||||
|
target.ApplyTemplate(); |
||||
|
Assert.IsType<TextBlock>(target.Child); |
||||
|
|
||||
|
root.Child = null; |
||||
|
root = new TestRoot |
||||
|
{ |
||||
|
DataTemplates = new DataTemplates |
||||
|
{ |
||||
|
new FuncDataTemplate<string>(x => new Decorator()), |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
root.Child = target; |
||||
|
target.ApplyTemplate(); |
||||
|
Assert.IsType<Decorator>(target.Child); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
// 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.Presenters; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests.Primitives |
||||
|
{ |
||||
|
public class PopupRootTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void PopupRoot_IsAttachedToLogicalTree_Is_True() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
Assert.True(((ILogical)target).IsAttachedToLogicalTree); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Templated_Child_IsAttachedToLogicalTree_Is_True() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var target = CreateTarget(); |
||||
|
|
||||
|
Assert.True(target.Presenter.IsAttachedToLogicalTree); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var child = new Decorator(); |
||||
|
var target = CreateTarget(); |
||||
|
var window = new Window(); |
||||
|
var detachedCount = 0; |
||||
|
var attachedCount = 0; |
||||
|
|
||||
|
target.Content = child; |
||||
|
|
||||
|
target.DetachedFromLogicalTree += (s, e) => ++detachedCount; |
||||
|
child.DetachedFromLogicalTree += (s, e) => ++detachedCount; |
||||
|
target.AttachedToLogicalTree += (s, e) => ++attachedCount; |
||||
|
child.AttachedToLogicalTree += (s, e) => ++attachedCount; |
||||
|
|
||||
|
((ISetLogicalParent)target).SetParent(window); |
||||
|
|
||||
|
Assert.Equal(2, detachedCount); |
||||
|
Assert.Equal(2, attachedCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Detaching_PopupRoot_From_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree() |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
||||
|
{ |
||||
|
var child = new Decorator(); |
||||
|
var target = CreateTarget(); |
||||
|
var window = new Window(); |
||||
|
var detachedCount = 0; |
||||
|
var attachedCount = 0; |
||||
|
|
||||
|
target.Content = child; |
||||
|
((ISetLogicalParent)target).SetParent(window); |
||||
|
|
||||
|
target.DetachedFromLogicalTree += (s, e) => ++detachedCount; |
||||
|
child.DetachedFromLogicalTree += (s, e) => ++detachedCount; |
||||
|
target.AttachedToLogicalTree += (s, e) => ++attachedCount; |
||||
|
child.AttachedToLogicalTree += (s, e) => ++attachedCount; |
||||
|
|
||||
|
((ISetLogicalParent)target).SetParent(null); |
||||
|
|
||||
|
// Despite being detached from the parent logical tree, we're still attached to a
|
||||
|
// logical tree as PopupRoot itself is a logical tree root.
|
||||
|
Assert.True(((ILogical)target).IsAttachedToLogicalTree); |
||||
|
Assert.True(((ILogical)child).IsAttachedToLogicalTree); |
||||
|
Assert.Equal(2, detachedCount); |
||||
|
Assert.Equal(2, attachedCount); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private PopupRoot CreateTarget() |
||||
|
{ |
||||
|
var result = new PopupRoot |
||||
|
{ |
||||
|
Template = new FuncControlTemplate<PopupRoot>(_ => |
||||
|
new ContentPresenter |
||||
|
{ |
||||
|
Name = "PART_ContentPresenter", |
||||
|
}), |
||||
|
}; |
||||
|
|
||||
|
result.ApplyTemplate(); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,214 @@ |
|||||
|
// 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 Xunit; |
||||
|
|
||||
|
namespace Avalonia.Input.UnitTests |
||||
|
{ |
||||
|
public class KeyboardNavigationTests_Custom |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Tab_Should_Custom_Navigate_Within_Children() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(current = new Button { Content = "Button 1" }), |
||||
|
new Button { Content = "Button 2" }, |
||||
|
(next = new Button { Content = "Button 3" }), |
||||
|
}, |
||||
|
NextControl = next, |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Right_Should_Custom_Navigate_Within_Children() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(current = new Button { Content = "Button 1" }), |
||||
|
new Button { Content = "Button 2" }, |
||||
|
(next = new Button { Content = "Button 3" }), |
||||
|
}, |
||||
|
NextControl = next, |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Right); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Tab_Should_Custom_Navigate_From_Outside() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
new Button { Content = "Button 1" }, |
||||
|
new Button { Content = "Button 2" }, |
||||
|
(next = new Button { Content = "Button 3" }), |
||||
|
}, |
||||
|
NextControl = next, |
||||
|
}; |
||||
|
|
||||
|
var root = new StackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(current = new Button { Content = "Outside" }), |
||||
|
target, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Tab_Should_Custom_Navigate_From_Outside_When_Wrapping() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
new Button { Content = "Button 1" }, |
||||
|
new Button { Content = "Button 2" }, |
||||
|
(next = new Button { Content = "Button 3" }), |
||||
|
}, |
||||
|
NextControl = next, |
||||
|
}; |
||||
|
|
||||
|
var root = new StackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
target, |
||||
|
(current = new Button { Content = "Outside" }), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ShiftTab_Should_Custom_Navigate_From_Outside() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
new Button { Content = "Button 1" }, |
||||
|
new Button { Content = "Button 2" }, |
||||
|
(next = new Button { Content = "Button 3" }), |
||||
|
}, |
||||
|
NextControl = next, |
||||
|
}; |
||||
|
|
||||
|
var root = new StackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(current = new Button { Content = "Outside" }), |
||||
|
target, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Previous); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Right_Should_Custom_Navigate_From_Outside() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
new Button { Content = "Button 1" }, |
||||
|
new Button { Content = "Button 2" }, |
||||
|
(next = new Button { Content = "Button 3" }), |
||||
|
}, |
||||
|
NextControl = next, |
||||
|
}; |
||||
|
|
||||
|
var root = new StackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(current = new Button { Content = "Outside" }), |
||||
|
target, |
||||
|
}, |
||||
|
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Right); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Tab_Should_Navigate_Outside_When_Null_Returned_As_Next() |
||||
|
{ |
||||
|
Button current; |
||||
|
Button next; |
||||
|
var target = new CustomNavigatingStackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
new Button { Content = "Button 1" }, |
||||
|
(current = new Button { Content = "Button 2" }), |
||||
|
new Button { Content = "Button 3" }, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
var root = new StackPanel |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
target, |
||||
|
(next = new Button { Content = "Outside" }), |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next); |
||||
|
|
||||
|
Assert.Same(next, result); |
||||
|
} |
||||
|
|
||||
|
private class CustomNavigatingStackPanel : StackPanel, ICustomKeyboardNavigation |
||||
|
{ |
||||
|
public bool CustomNavigates { get; set; } = true; |
||||
|
public IInputElement NextControl { get; set; } |
||||
|
|
||||
|
public (bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction) |
||||
|
{ |
||||
|
return (CustomNavigates, NextControl); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Layout.UnitTests |
||||
|
{ |
||||
|
internal class LayoutTestControl : Decorator |
||||
|
{ |
||||
|
public bool Measured { get; set; } |
||||
|
public bool Arranged { get; set; } |
||||
|
public Func<ILayoutable, Size, Size> DoMeasureOverride { get; set; } |
||||
|
public Func<ILayoutable, Size, Size> DoArrangeOverride { get; set; } |
||||
|
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
Measured = true; |
||||
|
return DoMeasureOverride != null ? |
||||
|
DoMeasureOverride(this, availableSize) : |
||||
|
base.MeasureOverride(availableSize); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
Arranged = true; |
||||
|
return DoArrangeOverride != null ? |
||||
|
DoArrangeOverride(this, finalSize) : |
||||
|
base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
// 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.UnitTests; |
||||
|
|
||||
|
namespace Avalonia.Layout.UnitTests |
||||
|
{ |
||||
|
internal class LayoutTestRoot : TestRoot, ILayoutable |
||||
|
{ |
||||
|
public bool Measured { get; set; } |
||||
|
public bool Arranged { get; set; } |
||||
|
public Func<ILayoutable, Size, Size> DoMeasureOverride { get; set; } |
||||
|
public Func<ILayoutable, Size, Size> DoArrangeOverride { get; set; } |
||||
|
|
||||
|
void ILayoutable.Measure(Size availableSize) |
||||
|
{ |
||||
|
Measured = true; |
||||
|
Measure(availableSize); |
||||
|
} |
||||
|
|
||||
|
void ILayoutable.Arrange(Rect rect) |
||||
|
{ |
||||
|
Arranged = true; |
||||
|
Arrange(rect); |
||||
|
} |
||||
|
|
||||
|
protected override Size MeasureOverride(Size availableSize) |
||||
|
{ |
||||
|
return DoMeasureOverride != null ? |
||||
|
DoMeasureOverride(this, availableSize) : |
||||
|
base.MeasureOverride(availableSize); |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
Arranged = true; |
||||
|
return DoArrangeOverride != null ? |
||||
|
DoArrangeOverride(this, finalSize) : |
||||
|
base.ArrangeOverride(finalSize); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,90 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Moq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Layout.UnitTests |
||||
|
{ |
||||
|
public class LayoutableTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Only_Calls_LayoutManager_InvalidateMeasure_Once() |
||||
|
{ |
||||
|
var target = new Mock<ILayoutManager>(); |
||||
|
|
||||
|
using (Start(target.Object)) |
||||
|
{ |
||||
|
var control = new Decorator(); |
||||
|
var root = new LayoutTestRoot { Child = control }; |
||||
|
|
||||
|
root.Measure(Size.Infinity); |
||||
|
root.Arrange(new Rect(root.DesiredSize)); |
||||
|
target.ResetCalls(); |
||||
|
|
||||
|
control.InvalidateMeasure(); |
||||
|
control.InvalidateMeasure(); |
||||
|
|
||||
|
target.Verify(x => x.InvalidateMeasure(control), Times.Once()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Only_Calls_LayoutManager_InvalidateArrange_Once() |
||||
|
{ |
||||
|
var target = new Mock<ILayoutManager>(); |
||||
|
|
||||
|
using (Start(target.Object)) |
||||
|
{ |
||||
|
var control = new Decorator(); |
||||
|
var root = new LayoutTestRoot { Child = control }; |
||||
|
|
||||
|
root.Measure(Size.Infinity); |
||||
|
root.Arrange(new Rect(root.DesiredSize)); |
||||
|
target.ResetCalls(); |
||||
|
|
||||
|
control.InvalidateArrange(); |
||||
|
control.InvalidateArrange(); |
||||
|
|
||||
|
target.Verify(x => x.InvalidateArrange(control), Times.Once()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Attaching_Control_To_Tree_Invalidates_Parent_Measure() |
||||
|
{ |
||||
|
var target = new Mock<ILayoutManager>(); |
||||
|
|
||||
|
using (Start(target.Object)) |
||||
|
{ |
||||
|
var control = new Decorator(); |
||||
|
var root = new LayoutTestRoot { Child = control }; |
||||
|
|
||||
|
root.Measure(Size.Infinity); |
||||
|
root.Arrange(new Rect(root.DesiredSize)); |
||||
|
Assert.True(control.IsMeasureValid); |
||||
|
|
||||
|
root.Child = null; |
||||
|
root.Measure(Size.Infinity); |
||||
|
root.Arrange(new Rect(root.DesiredSize)); |
||||
|
|
||||
|
Assert.False(control.IsMeasureValid); |
||||
|
Assert.True(root.IsMeasureValid); |
||||
|
|
||||
|
target.ResetCalls(); |
||||
|
|
||||
|
root.Child = control; |
||||
|
|
||||
|
Assert.False(root.IsMeasureValid); |
||||
|
Assert.False(control.IsMeasureValid); |
||||
|
target.Verify(x => x.InvalidateMeasure(root), Times.Once()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private IDisposable Start(ILayoutManager layoutManager) |
||||
|
{ |
||||
|
var result = AvaloniaLocator.EnterScope(); |
||||
|
AvaloniaLocator.CurrentMutable.Bind<ILayoutManager>().ToConstant(layoutManager); |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,24 +0,0 @@ |
|||||
// 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; |
|
||||
|
|
||||
namespace Avalonia.Layout.UnitTests |
|
||||
{ |
|
||||
internal class TestLayoutRoot : Decorator, ILayoutRoot |
|
||||
{ |
|
||||
public TestLayoutRoot() |
|
||||
{ |
|
||||
ClientSize = new Size(500, 500); |
|
||||
} |
|
||||
|
|
||||
public Size ClientSize |
|
||||
{ |
|
||||
get; |
|
||||
set; |
|
||||
} |
|
||||
|
|
||||
public Size MaxClientSize => Size.Infinity; |
|
||||
public double LayoutScaling => 1; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,4 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<packages> |
||||
|
<package id="Cake" version="0.18.0" /> |
||||
|
</packages> |
||||
Loading…
Reference in new issue