Browse Source

Merge remote-tracking branch 'origin/master' into feature/experimental-acrylic-brush

feature/test-branch
Dan Walmsley 6 years ago
parent
commit
aa839211f0
  1. 1
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  2. 11
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  3. 20
      src/Avalonia.Controls/IndexPath.cs
  4. 47
      src/Avalonia.Controls/IndexRange.cs
  5. 76
      src/Avalonia.Controls/SelectionModel.cs
  6. 2
      src/Avalonia.Controls/SelectionModelChangeSet.cs
  7. 22
      src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs
  8. 27
      src/Avalonia.Controls/SelectionNode.cs
  9. 18
      src/Avalonia.Controls/TreeView.cs
  10. 8
      src/Avalonia.Controls/Utils/SelectionTreeHelper.cs
  11. 17
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  12. 38
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  13. 24
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  14. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
  15. 82
      tests/Avalonia.Controls.UnitTests/IndexRangeTests.cs
  16. 146
      tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs
  17. 24
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  18. 548
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

1
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -20,6 +20,7 @@
<Button Command="{Binding AddItemCommand}">Add</Button>
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<Button Command="{Binding SelectRandomItemCommand}">Select Random</Button>
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
<ComboBoxItem>Single</ComboBoxItem>

11
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@ -23,12 +23,14 @@ namespace ControlCatalog.ViewModels
AddItemCommand = ReactiveCommand.Create(AddItem);
RemoveItemCommand = ReactiveCommand.Create(RemoveItem);
SelectRandomItemCommand = ReactiveCommand.Create(SelectRandomItem);
}
public ObservableCollection<Node> Items { get; }
public SelectionModel Selection { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public ReactiveCommand<Unit, Unit> SelectRandomItemCommand { get; }
public SelectionMode SelectionMode
{
@ -74,6 +76,15 @@ namespace ControlCatalog.ViewModels
}
}
private void SelectRandomItem()
{
var random = new Random();
var depth = random.Next(4);
var indexes = Enumerable.Range(0, 4).Select(x => random.Next(10));
var path = new IndexPath(indexes);
Selection.SelectedIndex = path;
}
private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
var selected = string.Join(",", e.SelectedIndices);

20
src/Avalonia.Controls/IndexPath.cs

@ -123,6 +123,26 @@ namespace Avalonia.Controls
}
}
public bool IsAncestorOf(in IndexPath other)
{
if (other.GetSize() <= GetSize())
{
return false;
}
var size = GetSize();
for (int i = 0; i < size; i++)
{
if (GetAt(i) != other.GetAt(i))
{
return false;
}
}
return true;
}
public override string ToString()
{
if (_path != null)

47
src/Avalonia.Controls/IndexRange.cs

@ -132,6 +132,53 @@ namespace Avalonia.Controls
return result;
}
public static int Intersect(
IList<IndexRange> ranges,
IndexRange range,
IList<IndexRange>? removed = null)
{
var result = 0;
for (var i = 0; i < ranges.Count && range != s_invalid; ++i)
{
var existing = ranges[i];
if (existing.End < range.Begin || existing.Begin > range.End)
{
removed?.Add(existing);
ranges.RemoveAt(i--);
result += existing.Count;
}
else
{
if (existing.Begin < range.Begin)
{
var except = new IndexRange(existing.Begin, range.Begin - 1);
removed?.Add(except);
ranges[i] = existing = new IndexRange(range.Begin, existing.End);
result += except.Count;
}
if (existing.End > range.End)
{
var except = new IndexRange(range.End + 1, existing.End);
removed?.Add(except);
ranges[i] = new IndexRange(existing.Begin, range.End);
result += except.Count;
}
}
}
MergeRanges(ranges);
if (removed is object)
{
MergeRanges(removed);
}
return result;
}
public static int Remove(
IList<IndexRange> ranges,
IndexRange range,

76
src/Avalonia.Controls/SelectionModel.cs

@ -46,17 +46,25 @@ namespace Avalonia.Controls
if (_rootNode.Source != null)
{
if (_rootNode.Source != null)
// Temporarily prevent auto-select when switching source.
var restoreAutoSelect = _autoSelect;
_autoSelect = false;
try
{
using (var operation = new Operation(this))
{
ClearSelection(resetAnchor: true);
}
}
finally
{
_autoSelect = restoreAutoSelect;
}
}
_rootNode.Source = value;
ApplyAutoSelect();
ApplyAutoSelect(true);
RaisePropertyChanged("Source");
@ -114,7 +122,7 @@ namespace Avalonia.Controls
if (_autoSelect != value)
{
_autoSelect = value;
ApplyAutoSelect();
ApplyAutoSelect(true);
}
}
}
@ -133,7 +141,7 @@ namespace Avalonia.Controls
while (current?.AnchorIndex >= 0)
{
path.Add(current.AnchorIndex);
current = current.GetAt(current.AnchorIndex, false);
current = current.GetAt(current.AnchorIndex, false, default);
}
anchor = new IndexPath(path);
@ -188,7 +196,6 @@ namespace Avalonia.Controls
using var operation = new Operation(this);
ClearSelection(resetAnchor: true);
SelectWithPathImpl(value, select: true);
ApplyAutoSelect();
}
}
}
@ -384,21 +391,18 @@ namespace Avalonia.Controls
{
using var operation = new Operation(this);
SelectImpl(index, select: false);
ApplyAutoSelect();
}
public void Deselect(int groupIndex, int itemIndex)
{
using var operation = new Operation(this);
SelectWithGroupImpl(groupIndex, itemIndex, select: false);
ApplyAutoSelect();
}
public void DeselectAt(IndexPath index)
{
using var operation = new Operation(this);
SelectWithPathImpl(index, select: false);
ApplyAutoSelect();
}
public bool IsSelected(int index) => _rootNode.IsSelected(index);
@ -416,7 +420,7 @@ namespace Avalonia.Controls
for (int i = 0; i < path.GetSize() - 1; i++)
{
var childIndex = path.GetAt(i);
node = node.GetAt(childIndex, realizeChild: false);
node = node.GetAt(childIndex, false, default);
if (node == null)
{
@ -451,7 +455,7 @@ namespace Avalonia.Controls
}
var isSelected = (bool?)false;
var childNode = _rootNode.GetAt(groupIndex, realizeChild: false);
var childNode = _rootNode.GetAt(groupIndex, false, default);
if (childNode != null)
{
@ -470,7 +474,7 @@ namespace Avalonia.Controls
for (int i = 0; i < path.GetSize() - 1; i++)
{
var childIndex = path.GetAt(i);
node = node.GetAt(childIndex, realizeChild: false);
node = node.GetAt(childIndex, false, default);
if (node == null)
{
@ -565,7 +569,6 @@ namespace Avalonia.Controls
{
using var operation = new Operation(this);
ClearSelection(resetAnchor: true);
ApplyAutoSelect();
}
public IDisposable Update() => new Operation(this);
@ -592,10 +595,13 @@ namespace Avalonia.Controls
}
OnSelectionChanged(e);
ApplyAutoSelect();
ApplyAutoSelect(true);
}
internal IObservable<object?>? ResolvePath(object data, IndexPath dataIndexPath)
internal IObservable<object?>? ResolvePath(
object data,
IndexPath dataIndexPath,
IndexPath finalIndexPath)
{
IObservable<object?>? resolved = null;
@ -604,18 +610,22 @@ namespace Avalonia.Controls
{
if (_childrenRequestedEventArgs == null)
{
_childrenRequestedEventArgs = new SelectionModelChildrenRequestedEventArgs(data, dataIndexPath, false);
_childrenRequestedEventArgs = new SelectionModelChildrenRequestedEventArgs(
data,
dataIndexPath,
finalIndexPath,
false);
}
else
{
_childrenRequestedEventArgs.Initialize(data, dataIndexPath, false);
_childrenRequestedEventArgs.Initialize(data, dataIndexPath, finalIndexPath, false);
}
ChildrenRequested(this, _childrenRequestedEventArgs);
resolved = _childrenRequestedEventArgs.Children;
// Clear out the values in the args so that it cannot be used after the event handler call.
_childrenRequestedEventArgs.Initialize(null, default, true);
_childrenRequestedEventArgs.Initialize(null, default, default, true);
}
return resolved;
@ -632,6 +642,8 @@ namespace Avalonia.Controls
{
AnchorIndex = default;
}
OnSelectionChanged();
}
private void OnSelectionChanged(SelectionModelSelectionChangedEventArgs? e = null)
@ -667,6 +679,8 @@ namespace Avalonia.Controls
{
AnchorIndex = new IndexPath(index);
}
OnSelectionChanged();
}
private void SelectWithGroupImpl(int groupIndex, int itemIndex, bool select)
@ -676,13 +690,15 @@ namespace Avalonia.Controls
ClearSelection(resetAnchor: true);
}
var childNode = _rootNode.GetAt(groupIndex, realizeChild: true);
var childNode = _rootNode.GetAt(groupIndex, true, new IndexPath(groupIndex, itemIndex));
var selected = childNode!.Select(itemIndex, select);
if (selected)
{
AnchorIndex = new IndexPath(groupIndex, itemIndex);
}
OnSelectionChanged();
}
private void SelectWithPathImpl(IndexPath index, bool select)
@ -711,6 +727,8 @@ namespace Avalonia.Controls
{
AnchorIndex = index;
}
OnSelectionChanged();
}
private void SelectRangeFromAnchorImpl(int index, bool select)
@ -724,6 +742,7 @@ namespace Avalonia.Controls
}
_rootNode.SelectRange(new IndexRange(anchorIndex, index), select);
OnSelectionChanged();
}
private void SelectRangeFromAnchorWithGroupImpl(int endGroupIndex, int endItemIndex, bool select)
@ -752,11 +771,13 @@ namespace Avalonia.Controls
for (int groupIdx = startGroupIndex; groupIdx <= endGroupIndex; groupIdx++)
{
var groupNode = _rootNode.GetAt(groupIdx, realizeChild: true)!;
var groupNode = _rootNode.GetAt(groupIdx, true, new IndexPath(endGroupIndex, endItemIndex))!;
int startIndex = groupIdx == startGroupIndex ? startItemIndex : 0;
int endIndex = groupIdx == endGroupIndex ? endItemIndex : groupNode.DataCount - 1;
groupNode.SelectRange(new IndexRange(startIndex, endIndex), select);
}
OnSelectionChanged();
}
private void SelectRangeImpl(IndexPath start, IndexPath end, bool select)
@ -784,6 +805,8 @@ namespace Avalonia.Controls
info.ParentNode!.Select(info.Path.GetAt(info.Path.GetSize() - 1), select);
}
});
OnSelectionChanged();
}
private void BeginOperation()
@ -806,6 +829,8 @@ namespace Avalonia.Controls
if (--_operationCount == 0)
{
ApplyAutoSelect(false);
var changes = new List<SelectionNodeOperation>();
_rootNode.EndOperation(changes);
@ -827,7 +852,7 @@ namespace Avalonia.Controls
}
}
private void ApplyAutoSelect()
private void ApplyAutoSelect(bool createOperation)
{
if (AutoSelect)
{
@ -835,8 +860,15 @@ namespace Avalonia.Controls
if (SelectedIndex == default && _rootNode.ItemsSourceView?.Count > 0)
{
using var operation = new Operation(this);
SelectImpl(0, true);
if (createOperation)
{
using var operation = new Operation(this);
SelectImpl(0, true);
}
else
{
SelectImpl(0, true);
}
}
}
}

2
src/Avalonia.Controls/SelectionModelChangeSet.cs

@ -135,7 +135,7 @@ namespace Avalonia.Controls
if (index >= currentIndex && index < currentIndex + currentCount)
{
int targetIndex = GetIndexAt(getRanges(info), index - currentIndex);
item = info.Items?.GetAt(targetIndex);
item = info.Items?.Count > targetIndex ? info.Items?.GetAt(targetIndex) : null;
break;
}

22
src/Avalonia.Controls/SelectionModelChildrenRequestedEventArgs.cs

@ -16,15 +16,17 @@ namespace Avalonia.Controls
{
private object? _source;
private IndexPath _sourceIndexPath;
private IndexPath _finalIndexPath;
private bool _throwOnAccess;
internal SelectionModelChildrenRequestedEventArgs(
object source,
IndexPath sourceIndexPath,
IndexPath finalIndexPath,
bool throwOnAccess)
{
source = source ?? throw new ArgumentNullException(nameof(source));
Initialize(source, sourceIndexPath, throwOnAccess);
Initialize(source, sourceIndexPath, finalIndexPath, throwOnAccess);
}
/// <summary>
@ -65,9 +67,26 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Gets the index of the final object which is being attempted to be retrieved.
/// </summary>
public IndexPath FinalIndex
{
get
{
if (_throwOnAccess)
{
throw new ObjectDisposedException(nameof(SelectionModelChildrenRequestedEventArgs));
}
return _finalIndexPath;
}
}
internal void Initialize(
object? source,
IndexPath sourceIndexPath,
IndexPath finalIndexPath,
bool throwOnAccess)
{
if (!throwOnAccess && source == null)
@ -77,6 +96,7 @@ namespace Avalonia.Controls
_source = source;
_sourceIndexPath = sourceIndexPath;
_finalIndexPath = finalIndexPath;
_throwOnAccess = throwOnAccess;
}
}

27
src/Avalonia.Controls/SelectionNode.cs

@ -101,6 +101,7 @@ namespace Avalonia.Controls
ItemsSourceView = newDataSource;
TrimInvalidSelections();
PopulateSelectedItemsFromSelectedIndices();
HookupCollectionChangedHandler();
OnSelectionChanged();
@ -108,6 +109,26 @@ namespace Avalonia.Controls
}
}
private void TrimInvalidSelections()
{
if (_selected == null || ItemsSourceView == null)
{
return;
}
var validRange = ItemsSourceView.Count > 0 ? new IndexRange(0, ItemsSourceView.Count - 1) : new IndexRange(-1, -1);
var removed = new List<IndexRange>();
var removedCount = IndexRange.Intersect(_selected, validRange, removed);
if (removedCount > 0)
{
using var operation = _manager.Update();
SelectedCount -= removedCount;
OnSelectionChanged();
_operation!.Deselected(removed);
}
}
public ItemsSourceView? ItemsSourceView { get; private set; }
public int DataCount => ItemsSourceView?.Count ?? 0;
public int ChildrenNodeCount => _childrenNodes.Count;
@ -141,7 +162,7 @@ namespace Avalonia.Controls
// create a bunch of leaf node instances - instead i use the same instance m_leafNode to avoid
// an explosion of node objects. However, I'm still creating the m_childrenNodes
// collection unfortunately.
public SelectionNode? GetAt(int index, bool realizeChild)
public SelectionNode? GetAt(int index, bool realizeChild, IndexPath finalIndexPath)
{
SelectionNode? child = null;
@ -171,7 +192,7 @@ namespace Avalonia.Controls
if (childData != null)
{
var childDataIndexPath = IndexPath.CloneWithChildIndex(index);
resolver = _manager.ResolvePath(childData, childDataIndexPath);
resolver = _manager.ResolvePath(childData, childDataIndexPath, finalIndexPath);
}
if (resolver != null)
@ -843,7 +864,7 @@ namespace Avalonia.Controls
int notSelectedCount = 0;
for (int i = 0; i < ChildrenNodeCount; i++)
{
var child = GetAt(i, realizeChild: false);
var child = GetAt(i, false, default);
if (child != null)
{

18
src/Avalonia.Controls/TreeView.cs

@ -119,9 +119,10 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the selected item.
/// </summary>
/// <summary>
/// Gets or sets the selected item.
/// </summary>
/// <remarks>
/// Note that setting this property only currently works if the item is expanded to be visible.
/// To select non-expanded nodes use `Selection.SelectedIndex`.
/// </remarks>
public object SelectedItem
{
get => Selection.SelectedItem;
@ -346,7 +347,7 @@ namespace Avalonia.Controls
if (container != null)
{
container.BringIntoView();
DispatcherTimer.RunOnce(container.BringIntoView, TimeSpan.Zero);
}
}
}
@ -395,10 +396,17 @@ namespace Avalonia.Controls
private void OnSelectionModelChildrenRequested(object sender, SelectionModelChildrenRequestedEventArgs e)
{
var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as ItemsControl;
var container = ItemContainerGenerator.Index.ContainerFromItem(e.Source) as TreeViewItem;
if (container is object)
{
if (e.SourceIndex.IsAncestorOf(e.FinalIndex))
{
container.IsExpanded = true;
container.ApplyTemplate();
container.Presenter?.ApplyTemplate();
}
e.Children = Observable.CombineLatest(
container.GetObservable(TreeViewItem.IsExpandedProperty),
container.GetObservable(ItemsProperty),

8
src/Avalonia.Controls/Utils/SelectionTreeHelper.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Utils
if (depth < path.GetSize() - 1)
{
node = node.GetAt(childIndex, realizeChildren)!;
node = node.GetAt(childIndex, realizeChildren, path)!;
}
}
}
@ -50,7 +50,7 @@ namespace Avalonia.Controls.Utils
int count = realizeChildren ? nextNode.Node.DataCount : nextNode.Node.ChildrenNodeCount;
for (int i = count - 1; i >= 0; i--)
{
var child = nextNode.Node.GetAt(i, realizeChildren);
var child = nextNode.Node.GetAt(i, realizeChildren, nextNode.Path);
var childPath = nextNode.Path.CloneWithChildIndex(i);
if (child != null)
{
@ -90,7 +90,7 @@ namespace Avalonia.Controls.Utils
for (int i = endIndex; i >= startIndex; i--)
{
var child = node.GetAt(i, realizeChild: true);
var child = node.GetAt(i, true, end);
if (child != null)
{
var childPath = currentPath.CloneWithChildIndex(i);
@ -112,7 +112,7 @@ namespace Avalonia.Controls.Utils
int endIndex = depth < end.GetSize() && isEndPath ? end.GetAt(depth) : info.Node.DataCount - 1;
for (int i = endIndex; i >= startIndex; i--)
{
var child = info.Node.GetAt(i, realizeChild: true);
var child = info.Node.GetAt(i, true, end);
if (child != null)
{
var childPath = info.Path.CloneWithChildIndex(i);

17
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Threading;
namespace Avalonia.Diagnostics.ViewModels
{
@ -49,7 +50,21 @@ namespace Avalonia.Diagnostics.ViewModels
value is TreePageViewModel newTree &&
oldTree?.SelectedNode?.Visual is IControl control)
{
newTree.SelectControl(control);
// HACK: We want to select the currently selected control in the new tree, but
// to select nested nodes in TreeView, currently the TreeView has to be able to
// expand the parent nodes. Because at this point the TreeView isn't visible,
// this will fail unless we schedule the selection to run after layout.
DispatcherTimer.RunOnce(
() =>
{
try
{
newTree.SelectControl(control);
}
catch { }
},
TimeSpan.FromMilliseconds(0),
DispatcherPriority.ApplicationIdle);
}
RaiseAndSetIfChanged(ref _content, value);

38
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
@ -82,5 +82,41 @@ namespace Avalonia.Diagnostics.ViewModels
get;
private set;
}
public IndexPath Index
{
get
{
var indices = new List<int>();
var child = this;
var parent = Parent;
while (parent is object)
{
indices.Add(IndexOf(parent.Children, child));
child = child.Parent;
parent = parent.Parent;
}
indices.Add(0);
indices.Reverse();
return new IndexPath(indices);
}
}
private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item)
{
var count = collection.Count;
for (var i = 0; i < count; ++i)
{
if (collection[i] == item)
{
return i;
}
}
throw new AvaloniaInternalException("TreeNode was not present in parent Children collection.");
}
}
}

24
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -6,28 +6,40 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class TreePageViewModel : ViewModelBase, IDisposable
{
private TreeNode _selected;
private TreeNode _selectedNode;
private ControlDetailsViewModel _details;
private string _propertyFilter;
public TreePageViewModel(TreeNode[] nodes)
{
Nodes = nodes;
}
Selection = new SelectionModel
{
SingleSelect = true,
Source = Nodes
};
Selection.SelectionChanged += (s, e) =>
{
SelectedNode = (TreeNode)Selection.SelectedItem;
};
}
public TreeNode[] Nodes { get; protected set; }
public SelectionModel Selection { get; }
public TreeNode SelectedNode
{
get => _selected;
set
get => _selectedNode;
private set
{
if (Details != null)
{
_propertyFilter = Details.PropertyFilter;
}
if (RaiseAndSetIfChanged(ref _selected, value))
if (RaiseAndSetIfChanged(ref _selectedNode, value))
{
Details = value != null ?
new ControlDetailsViewModel(value.Visual, _propertyFilter) :
@ -83,8 +95,8 @@ namespace Avalonia.Diagnostics.ViewModels
if (node != null)
{
SelectedNode = node;
ExpandNode(node.Parent);
Selection.SelectedIndex = node.Index;
}
}

2
src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml

@ -6,7 +6,7 @@
<TreeView Name="tree"
BorderThickness="0"
Items="{Binding Nodes}"
SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
Selection="{Binding Selection}">
<TreeView.DataTemplates>
<TreeDataTemplate DataType="vm:TreeNode"
ItemsSource="{Binding Children}">

82
tests/Avalonia.Controls.UnitTests/IndexRangeTests.cs

@ -127,6 +127,88 @@ namespace Avalonia.Controls.UnitTests
Assert.Empty(selected);
}
[Fact]
public void Intersect_Should_Remove_Items_From_Beginning()
{
var ranges = new List<IndexRange> { new IndexRange(0, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(2, 12), removed);
Assert.Equal(2, result);
Assert.Equal(new[] { new IndexRange(2, 10) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 1) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Items_From_End()
{
var ranges = new List<IndexRange> { new IndexRange(0, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(0, 8), removed);
Assert.Equal(2, result);
Assert.Equal(new[] { new IndexRange(0, 8) }, ranges);
Assert.Equal(new[] { new IndexRange(9, 10) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_Range_Start()
{
var ranges = new List<IndexRange> { new IndexRange(0, 5), new IndexRange(6, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(6, 10), removed);
Assert.Equal(6, result);
Assert.Equal(new[] { new IndexRange(6, 10) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 5) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_Range_End()
{
var ranges = new List<IndexRange> { new IndexRange(0, 5), new IndexRange(6, 10) };
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(0, 4), removed);
Assert.Equal(6, result);
Assert.Equal(new[] { new IndexRange(0, 4) }, ranges);
Assert.Equal(new[] { new IndexRange(5, 10) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_Range_Start_End()
{
var ranges = new List<IndexRange>
{
new IndexRange(0, 2),
new IndexRange(3, 7),
new IndexRange(8, 10)
};
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(3, 7), removed);
Assert.Equal(6, result);
Assert.Equal(new[] { new IndexRange(3, 7) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 2), new IndexRange(8, 10) }, removed);
}
[Fact]
public void Intersect_Should_Remove_Entire_And_Partial_Range_Start_End()
{
var ranges = new List<IndexRange>
{
new IndexRange(0, 2),
new IndexRange(3, 7),
new IndexRange(8, 10)
};
var removed = new List<IndexRange>();
var result = IndexRange.Intersect(ranges, new IndexRange(4, 6), removed);
Assert.Equal(8, result);
Assert.Equal(new[] { new IndexRange(4, 6) }, ranges);
Assert.Equal(new[] { new IndexRange(0, 3), new IndexRange(7, 10) }, removed);
}
[Fact]
public void Remove_Should_Remove_Entire_Range()
{

146
tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs

@ -1512,6 +1512,47 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, raised);
}
[Fact]
public void Batch_Update_Selection_Is_Correct_Throughout()
{
var data = new[] { "foo", "bar", "baz", "qux" };
var target = new SelectionModel { Source = data };
var raised = 0;
using (target.Update())
{
target.Select(1);
Assert.Equal(new IndexPath(1), target.SelectedIndex);
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
target.Deselect(1);
Assert.Equal(new IndexPath(), target.SelectedIndex);
Assert.Empty(target.SelectedIndices);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
target.SelectRange(new IndexPath(1), new IndexPath(1));
Assert.Equal(new IndexPath(1), target.SelectedIndex);
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal("bar", target.SelectedItem);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
target.ClearSelection();
Assert.Equal(new IndexPath(), target.SelectedIndex);
Assert.Empty(target.SelectedIndices);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
}
Assert.Equal(0, raised);
}
[Fact]
public void AutoSelect_Selects_When_Enabled()
{
@ -1713,6 +1754,30 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, raised);
}
[Fact]
public void AutoSelect_Is_Applied_At_End_Of_Batch_Update()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
using (target.Update())
{
target.ClearSelection();
Assert.Equal(new IndexPath(), target.SelectedIndex);
Assert.Empty(target.SelectedIndices);
Assert.Null(target.SelectedItem);
Assert.Empty(target.SelectedItems);
}
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(new[] { new IndexPath(0) }, target.SelectedIndices);
Assert.Equal("foo", target.SelectedItem);
Assert.Equal(new[] { "foo" }, target.SelectedItems);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
}
[Fact]
public void Can_Replace_Parent_Children_Collection()
{
@ -1806,6 +1871,87 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, node.PropertyChangedSubscriptions);
}
[Fact]
public void Setting_SelectedIndex_To_Minus_1_Clears_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
target.SelectedIndex = new IndexPath(1);
target.SelectedIndex = new IndexPath(-1);
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Assigning_Source_With_Less_Items_Than_Previous_Clears_Selection()
{
var data = new[] { "foo", "bar", "baz", "boo", "hoo" };
var smallerData = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
target.Source = data;
target.SelectedIndex = new IndexPath(4);
target.Source = smallerData;
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Selection_Trims_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel();
target.SelectedIndex = new IndexPath(4);
target.Source = data;
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Selection_Trims_Selection_RetainSelection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
target.SelectedIndex = new IndexPath(4);
target.Source = data;
Assert.Empty(target.SelectedIndices);
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Multiple_Selection_Trims_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
target.Select(4);
target.Select(2);
target.Source = data;
Assert.Equal(1, target.SelectedIndices.Count);
Assert.Equal(new IndexPath(2), target.SelectedIndices.First());
}
[Fact]
public void Initializing_Source_With_Less_Items_Than_Selection_Raises_SelectionChanged()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel();
var raised = 0;
target.SelectedIndex = new IndexPath(4);
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Equal(new[] { Path(4) }, e.DeselectedIndices);
Assert.Equal(new object[] { null }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
++raised;
};
target.Source = data;
Assert.Equal(2, raised);
}
private int GetSubscriberCount(AvaloniaList<object> list)
{
return ((INotifyCollectionChangedDebug)list).GetCollectionChangedSubscribers()?.Length ?? 0;

24
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.UnitTests;
@ -325,6 +327,28 @@ namespace Avalonia.Controls.UnitTests
Assert.NotEqual(dataContext, tabItem.Content);
}
[Fact]
public void Can_Have_Empty_Tab_Control()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<TabControl Name='tabs' Items='{Binding Tabs}'/>
</Window>";
var loader = new Markup.Xaml.AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var tabControl = window.FindControl<TabControl>("tabs");
tabControl.DataContext = new { Tabs = new List<string>() };
window.ApplyTemplate();
Assert.Equal(0, tabControl.Items.Count());
}
}
private IControlTemplate TabControlTemplate()
{
return new FuncControlTemplate<TabControl>((parent, scope) =>

548
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -118,313 +118,340 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Clicking_Item_Should_Select_It()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container);
Assert.NotNull(container);
_mouse.Click(container);
_mouse.Click(container);
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
}
}
[Fact]
public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_It()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container);
Assert.NotNull(container);
target.SelectedItem = item;
target.SelectedItem = item;
Assert.True(container.IsSelected);
Assert.True(container.IsSelected);
_mouse.Click(container, modifiers: KeyModifiers.Control);
_mouse.Click(container, modifiers: KeyModifiers.Control);
Assert.Null(target.SelectedItem);
Assert.False(container.IsSelected);
Assert.Null(target.SelectedItem);
Assert.False(container.IsSelected);
}
}
[Fact]
public void Clicking_WithControlModifier_Not_Selected_Item_Should_Select_It()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var item1 = tree[0].Children[1].Children[0];
var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item1 = tree[0].Children[1].Children[0];
var container1 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2 = tree[0].Children[1];
var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
var item2 = tree[0].Children[1];
var container2 = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
Assert.NotNull(container1);
Assert.NotNull(container2);
Assert.NotNull(container1);
Assert.NotNull(container2);
target.SelectedItem = item1;
target.SelectedItem = item1;
Assert.True(container1.IsSelected);
Assert.True(container1.IsSelected);
_mouse.Click(container2, modifiers: KeyModifiers.Control);
Assert.Equal(item2, target.SelectedItem);
Assert.False(container1.IsSelected);
Assert.True(container2.IsSelected);
_mouse.Click(container2, modifiers: KeyModifiers.Control);
Assert.Equal(item2, target.SelectedItem);
Assert.False(container1.IsSelected);
Assert.True(container2.IsSelected);
}
}
[Fact]
public void Clicking_WithControlModifier_Selected_Item_Should_Deselect_And_Remove_From_SelectedItems()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var rootNode = tree[0];
var rootNode = tree[0];
var item1 = rootNode.Children[0];
var item2 = rootNode.Children.Last();
var item1 = rootNode.Children[0];
var item2 = rootNode.Children.Last();
var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
ClickContainer(item1Container, KeyModifiers.Control);
Assert.True(item1Container.IsSelected);
ClickContainer(item1Container, KeyModifiers.Control);
Assert.True(item1Container.IsSelected);
ClickContainer(item2Container, KeyModifiers.Control);
Assert.True(item2Container.IsSelected);
ClickContainer(item2Container, KeyModifiers.Control);
Assert.True(item2Container.IsSelected);
Assert.Equal(new[] {item1, item2}, target.Selection.SelectedItems.OfType<Node>());
Assert.Equal(new[] { item1, item2 }, target.Selection.SelectedItems.OfType<Node>());
ClickContainer(item1Container, KeyModifiers.Control);
Assert.False(item1Container.IsSelected);
ClickContainer(item1Container, KeyModifiers.Control);
Assert.False(item1Container.IsSelected);
Assert.DoesNotContain(item1, target.Selection.SelectedItems.OfType<Node>());
Assert.DoesNotContain(item1, target.Selection.SelectedItems.OfType<Node>());
}
}
[Fact]
public void Clicking_WithShiftModifier_DownDirection_Should_Select_Range_Of_Items()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var rootNode = tree[0];
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children.Last();
var from = rootNode.Children[0];
var to = rootNode.Children.Last();
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(fromContainer, KeyModifiers.None);
Assert.True(fromContainer.IsSelected);
Assert.True(fromContainer.IsSelected);
ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode);
}
}
[Fact]
public void Clicking_WithShiftModifier_UpDirection_Should_Select_Range_Of_Items()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var rootNode = tree[0];
var rootNode = tree[0];
var from = rootNode.Children.Last();
var to = rootNode.Children[0];
var from = rootNode.Children.Last();
var to = rootNode.Children[0];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(fromContainer, KeyModifiers.None);
Assert.True(fromContainer.IsSelected);
Assert.True(fromContainer.IsSelected);
ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode);
}
}
[Fact]
public void Clicking_First_Item_Of_SelectedItems_Should_Select_Only_It()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var rootNode = tree[0];
var rootNode = tree[0];
var from = rootNode.Children.Last();
var to = rootNode.Children[0];
var from = rootNode.Children.Last();
var to = rootNode.Children[0];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, KeyModifiers.Shift);
AssertChildrenSelected(target, rootNode);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(fromContainer, KeyModifiers.None);
Assert.True(fromContainer.IsSelected);
Assert.True(fromContainer.IsSelected);
foreach (var child in rootNode.Children)
{
if (child == from)
foreach (var child in rootNode.Children)
{
continue;
}
if (child == from)
{
continue;
}
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child);
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(child);
Assert.False(container.IsSelected);
Assert.False(container.IsSelected);
}
}
}
[Fact]
public void Setting_SelectedItem_Should_Set_Container_Selected()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
var item = tree[0].Children[1].Children[0];
var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
Assert.NotNull(container);
Assert.NotNull(container);
target.SelectedItem = item;
target.SelectedItem = item;
Assert.True(container.IsSelected);
Assert.True(container.IsSelected);
}
}
[Fact]
public void Setting_SelectedItem_Should_Raise_SelectedItemChanged_Event()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
ExpandAll(target);
var item = tree[0].Children[1].Children[0];
var item = tree[0].Children[1].Children[0];
var called = false;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.RemovedItems);
Assert.Equal(1, e.AddedItems.Count);
Assert.Same(item, e.AddedItems[0]);
called = true;
};
var called = false;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.RemovedItems);
Assert.Equal(1, e.AddedItems.Count);
Assert.Same(item, e.AddedItems[0]);
called = true;
};
target.SelectedItem = item;
Assert.True(called);
target.SelectedItem = item;
Assert.True(called);
}
}
[Fact]
@ -564,7 +591,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
{
using (UnitTestApplication.Start(TestServices.RealFocus))
using (Application())
{
var focus = FocusManager.Instance;
var navigation = AvaloniaLocator.Current.GetService<IKeyboardNavigationHandler>();
@ -647,7 +674,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
using (Application())
{
var tree = CreateTestTreeData();
var target = new TreeView
@ -694,7 +721,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
using (Application())
{
var tree = CreateTestTreeData();
var target = new TreeView
@ -768,97 +795,106 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
var rootNode = tree[0];
var to = rootNode.Children[0];
var then = rootNode.Children[1];
var rootNode = tree[0];
var to = rootNode.Children[0];
var then = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then);
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(rootNode);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var thenContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(then);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift);
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift);
Assert.Equal(2, target.Selection.SelectedItems.Count);
Assert.Equal(2, target.Selection.SelectedItems.Count);
_mouse.Click(thenContainer, MouseButton.Right);
_mouse.Click(thenContainer, MouseButton.Right);
Assert.Equal(1, target.Selection.SelectedItems.Count);
Assert.Equal(1, target.Selection.SelectedItems.Count);
}
}
[Fact]
public void Shift_Right_Click_Should_Not_Select_Multiple()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
_mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift);
_mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Shift);
Assert.Equal(1, target.Selection.SelectedItems.Count);
Assert.Equal(1, target.Selection.SelectedItems.Count);
}
}
[Fact]
public void Ctrl_Right_Click_Should_Not_Select_Multiple()
{
var tree = CreateTestTreeData();
var target = new TreeView
using (Application())
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple,
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
CreateNodeDataTemplate(target);
ApplyTemplates(target);
target.ExpandSubTree((TreeViewItem)target.Presenter.Panel.Children[0]);
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children[1];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
_mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control);
_mouse.Click(fromContainer);
_mouse.Click(toContainer, MouseButton.Right, modifiers: KeyModifiers.Control);
Assert.Equal(1, target.Selection.SelectedItems.Count);
Assert.Equal(1, target.Selection.SelectedItems.Count);
}
}
[Fact]
@ -944,7 +980,7 @@ namespace Avalonia.Controls.UnitTests
public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
{
/// Issue #2980.
using (UnitTestApplication.Start(TestServices.RealStyler))
using (Application())
{
var target = new DerivedTreeView
{
@ -1183,12 +1219,12 @@ namespace Avalonia.Controls.UnitTests
}
}
void ClickContainer(IControl container, KeyModifiers modifiers)
private void ClickContainer(IControl container, KeyModifiers modifiers)
{
_mouse.Click(container, modifiers: modifiers);
}
void AssertChildrenSelected(TreeView treeView, Node rootNode)
private void AssertChildrenSelected(TreeView treeView, Node rootNode)
{
foreach (var child in rootNode.Children)
{
@ -1198,6 +1234,16 @@ namespace Avalonia.Controls.UnitTests
}
}
private IDisposable Application()
{
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: new KeyboardNavigationHandler(),
inputManager: new InputManager()));
}
private class Node : NotifyingBase
{
private IAvaloniaList<Node> _children;

Loading…
Cancel
Save