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

2322 lines
77 KiB

// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Diagnostics;
using ReactiveUI;
using Xunit;
using Xunit.Abstractions;
namespace Avalonia.Controls.UnitTests
{
public class SelectionModelTests
{
private readonly ITestOutputHelper _output;
public SelectionModelTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public void ValidateOneLevelSingleSelectionNoSource()
{
SelectionModel selectionModel = new SelectionModel() { SingleSelect = true };
_output.WriteLine("No source set.");
Select(selectionModel, 4, true);
ValidateSelection(selectionModel, Path(4));
Select(selectionModel, 4, false);
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateOneLevelSingleSelection()
{
SelectionModel selectionModel = new SelectionModel() { SingleSelect = true };
_output.WriteLine("Set the source to 10 items");
selectionModel.Source = Enumerable.Range(0, 10).ToList();
Select(selectionModel, 3, true);
ValidateSelection(selectionModel, Path(3));
Select(selectionModel, 3, false);
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateSelectionChangedEvent()
{
SelectionModel selectionModel = new SelectionModel();
selectionModel.Source = Enumerable.Range(0, 10).ToList();
int selectionChangedFiredCount = 0;
selectionModel.SelectionChanged += delegate (object sender, SelectionModelSelectionChangedEventArgs args)
{
selectionChangedFiredCount++;
ValidateSelection(selectionModel, Path(4));
};
Select(selectionModel, 4, true);
ValidateSelection(selectionModel, Path(4));
Assert.Equal(1, selectionChangedFiredCount);
}
[Fact]
public void ValidateCanSetSelectedIndex()
{
var model = new SelectionModel();
var ip = IndexPath.CreateFrom(34);
model.SelectedIndex = ip;
Assert.Equal(0, ip.CompareTo(model.SelectedIndex));
}
[Fact]
public void ValidateOneLevelMultipleSelection()
{
SelectionModel selectionModel = new SelectionModel();
selectionModel.Source = Enumerable.Range(0, 10).ToList();
Select(selectionModel, 4, true);
ValidateSelection(selectionModel, Path(4));
SelectRangeFromAnchor(selectionModel, 8, true /* select */);
ValidateSelection(selectionModel,
Path(4),
Path(5),
Path(6),
Path(7),
Path(8));
ClearSelection(selectionModel);
SetAnchorIndex(selectionModel, 6);
SelectRangeFromAnchor(selectionModel, 3, true /* select */);
ValidateSelection(selectionModel,
Path(3),
Path(4),
Path(5),
Path(6));
SetAnchorIndex(selectionModel, 4);
SelectRangeFromAnchor(selectionModel, 5, false /* select */);
ValidateSelection(selectionModel,
Path(3),
Path(6));
}
[Fact]
public void ValidateTwoLevelSingleSelection()
{
SelectionModel selectionModel = new SelectionModel();
_output.WriteLine("Setting the source");
selectionModel.Source = CreateNestedData(1 /* levels */ , 2 /* groupsAtLevel */, 2 /* countAtLeaf */);
Select(selectionModel, 1, 1, true);
ValidateSelection(selectionModel, Path(1, 1));
Select(selectionModel, 1, 1, false);
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateTwoLevelMultipleSelection()
{
SelectionModel selectionModel = new SelectionModel();
_output.WriteLine("Setting the source");
selectionModel.Source = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */);
Select(selectionModel, 1, 2, true);
ValidateSelection(selectionModel, Path(1, 2));
SelectRangeFromAnchor(selectionModel, 2, 2, true /* select */);
ValidateSelection(selectionModel,
Path(1, 2),
Path(2, 0),
Path(2, 1),
Path(2, 2));
ClearSelection(selectionModel);
SetAnchorIndex(selectionModel, 2, 1);
SelectRangeFromAnchor(selectionModel, 0, 1, true /* select */);
ValidateSelection(selectionModel,
Path(0, 1),
Path(0, 2),
Path(1, 0),
Path(1, 1),
Path(1, 2),
Path(2, 0),
Path(2, 1));
SetAnchorIndex(selectionModel, 1, 1);
SelectRangeFromAnchor(selectionModel, 2, 0, false /* select */);
ValidateSelection(selectionModel,
Path(0, 1),
Path(0, 2),
Path(1, 0),
Path(2, 1));
ClearSelection(selectionModel);
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateNestedSingleSelection()
{
SelectionModel selectionModel = new SelectionModel() { SingleSelect = true };
_output.WriteLine("Setting the source");
selectionModel.Source = CreateNestedData(3 /* levels */ , 2 /* groupsAtLevel */, 2 /* countAtLeaf */);
var path = Path(1, 0, 1, 1);
Select(selectionModel, path, true);
ValidateSelection(selectionModel, path);
Select(selectionModel, Path(0, 0, 1, 0), true);
ValidateSelection(selectionModel, Path(0, 0, 1, 0));
Select(selectionModel, Path(0, 0, 1, 0), false);
ValidateSelection(selectionModel);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ValidateNestedMultipleSelection(bool handleChildrenRequested)
{
SelectionModel selectionModel = new SelectionModel();
List<IndexPath> sourcePaths = new List<IndexPath>();
_output.WriteLine("Setting the source");
selectionModel.Source = CreateNestedData(3 /* levels */ , 2 /* groupsAtLevel */, 4 /* countAtLeaf */);
if (handleChildrenRequested)
{
selectionModel.ChildrenRequested += (object sender, SelectionModelChildrenRequestedEventArgs args) =>
{
_output.WriteLine("ChildrenRequestedIndexPath:" + args.SourceIndex);
sourcePaths.Add(args.SourceIndex);
args.Children = Observable.Return(args.Source as IEnumerable);
};
}
var startPath = Path(1, 0, 1, 0);
Select(selectionModel, startPath, true);
ValidateSelection(selectionModel, startPath);
var endPath = Path(1, 1, 1, 0);
SelectRangeFromAnchor(selectionModel, endPath, true /* select */);
if (handleChildrenRequested)
{
// Validate SourceIndices.
var expectedSourceIndices = new List<IndexPath>()
{
Path(1),
Path(1, 0),
Path(1, 0, 1),
Path(1, 1),
Path(1, 0, 1, 3),
Path(1, 0, 1, 2),
Path(1, 0, 1, 1),
Path(1, 0, 1, 0),
Path(1, 1, 1),
Path(1, 1, 0),
Path(1, 1, 0, 3),
Path(1, 1, 0, 2),
Path(1, 1, 0, 1),
Path(1, 1, 0, 0),
Path(1, 1, 1, 0)
};
Assert.Equal(expectedSourceIndices.Count, sourcePaths.Count);
for (int i = 0; i < expectedSourceIndices.Count; i++)
{
Assert.True(AreEqual(expectedSourceIndices[i], sourcePaths[i]));
}
}
ValidateSelection(selectionModel,
Path(1, 1),
Path(1, 0, 1, 0),
Path(1, 0, 1, 1),
Path(1, 0, 1, 2),
Path(1, 0, 1, 3),
Path(1, 1, 0),
Path(1, 1, 1),
Path(1, 1, 0, 0),
Path(1, 1, 0, 1),
Path(1, 1, 0, 2),
Path(1, 1, 0, 3),
Path(1, 1, 1, 0));
ClearSelection(selectionModel);
ValidateSelection(selectionModel);
startPath = Path(0, 1, 0, 2);
SetAnchorIndex(selectionModel, startPath);
endPath = Path(0, 0, 0, 2);
SelectRangeFromAnchor(selectionModel, endPath, true /* select */);
ValidateSelection(selectionModel,
Path(0, 1),
Path(0, 0, 1),
Path(0, 0, 0, 2),
Path(0, 0, 0, 3),
Path(0, 0, 1, 0),
Path(0, 0, 1, 1),
Path(0, 0, 1, 2),
Path(0, 0, 1, 3),
Path(0, 1, 0),
Path(0, 1, 0, 0),
Path(0, 1, 0, 1),
Path(0, 1, 0, 2));
startPath = Path(0, 1, 0, 2);
SetAnchorIndex(selectionModel, startPath);
endPath = Path(0, 0, 0, 2);
SelectRangeFromAnchor(selectionModel, endPath, false /* select */);
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateInserts()
{
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(3);
selectionModel.Select(4);
selectionModel.Select(5);
ValidateSelection(selectionModel,
Path(3),
Path(4),
Path(5));
_output.WriteLine("Insert in selected range: Inserting 3 items at index 4");
data.Insert(4, 41);
data.Insert(4, 42);
data.Insert(4, 43);
ValidateSelection(selectionModel,
Path(3),
Path(7),
Path(8));
_output.WriteLine("Insert before selected range: Inserting 3 items at index 0");
data.Insert(0, 100);
data.Insert(0, 101);
data.Insert(0, 102);
ValidateSelection(selectionModel,
Path(6),
Path(10),
Path(11));
_output.WriteLine("Insert after selected range: Inserting 3 items at index 12");
data.Insert(12, 1000);
data.Insert(12, 1001);
data.Insert(12, 1002);
ValidateSelection(selectionModel,
Path(6),
Path(10),
Path(11));
}
[Fact]
public void ValidateGroupInserts()
{
var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */);
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(1, 1);
ValidateSelection(selectionModel, Path(1, 1));
_output.WriteLine("Insert before selected range: Inserting item at group index 0");
data.Insert(0, 100);
ValidateSelection(selectionModel, Path(2, 1));
_output.WriteLine("Insert after selected range: Inserting item at group index 3");
data.Insert(3, 1000);
ValidateSelection(selectionModel, Path(2, 1));
}
[Fact]
public void ValidateRemoves()
{
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(6);
selectionModel.Select(7);
selectionModel.Select(8);
ValidateSelection(selectionModel,
Path(6),
Path(7),
Path(8));
_output.WriteLine("Remove before selected range: Removing item at index 0");
data.RemoveAt(0);
ValidateSelection(selectionModel,
Path(5),
Path(6),
Path(7));
_output.WriteLine("Remove from before to middle of selected range: Removing items at index 3, 4, 5");
data.RemoveAt(3);
data.RemoveAt(3);
data.RemoveAt(3);
ValidateSelection(selectionModel, Path(3), Path(4));
_output.WriteLine("Remove after selected range: Removing item at index 5");
data.RemoveAt(5);
ValidateSelection(selectionModel, Path(3), Path(4));
}
[Fact]
public void ValidateGroupRemoves()
{
var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */);
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(1, 1);
selectionModel.Select(1, 2);
ValidateSelection(selectionModel, Path(1, 1), Path(1, 2));
_output.WriteLine("Remove before selected range: Removing item at group index 0");
data.RemoveAt(0);
ValidateSelection(selectionModel, Path(0, 1), Path(0, 2));
_output.WriteLine("Remove after selected range: Removing item at group index 1");
data.RemoveAt(1);
ValidateSelection(selectionModel, Path(0, 1), Path(0, 2));
_output.WriteLine("Remove group containing selected items");
var raised = 0;
selectionModel.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new object[] { 4, 5, }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
data.RemoveAt(0);
ValidateSelection(selectionModel);
Assert.Equal(1, raised);
}
[Fact]
public void CanReplaceItem()
{
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(3);
selectionModel.Select(4);
selectionModel.Select(5);
ValidateSelection(selectionModel, Path(3), Path(4), Path(5));
data[3] = 300;
data[4] = 400;
ValidateSelection(selectionModel, Path(5));
}
[Fact]
public void ValidateGroupReplaceLosesSelection()
{
var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */);
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(1, 1);
ValidateSelection(selectionModel, Path(1, 1));
data[1] = new ObservableCollection<int>(Enumerable.Range(0, 5));
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateClear()
{
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(3);
selectionModel.Select(4);
selectionModel.Select(5);
ValidateSelection(selectionModel, Path(3), Path(4), Path(5));
data.Clear();
ValidateSelection(selectionModel);
}
[Fact]
public void ValidateGroupClear()
{
var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */);
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.Select(1, 1);
ValidateSelection(selectionModel, Path(1, 1));
(data[1] as IList).Clear();
ValidateSelection(selectionModel);
}
// In some cases the leaf node might get a collection change that affects an ancestors selection
// state. In this case we were not raising selection changed event. For example, if all elements
// in a group are selected and a new item gets inserted - the parent goes from selected to partially
// selected. In that case we need to raise the selection changed event so that the header containers
// can show the correct visual.
[Fact]
public void ValidateEventWhenInnerNodeChangesSelectionState()
{
bool selectionChangedRaised = false;
var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */);
var selectionModel = new SelectionModel();
selectionModel.Source = data;
selectionModel.SelectionChanged += (sender, args) => { selectionChangedRaised = true; };
selectionModel.Select(1, 0);
selectionModel.Select(1, 1);
selectionModel.Select(1, 2);
ValidateSelection(selectionModel, Path(1, 0), Path(1, 1), Path(1, 2));
_output.WriteLine("Inserting 1.0");
selectionChangedRaised = false;
(data[1] as AvaloniaList<object>).Insert(0, 100);
Assert.True(selectionChangedRaised, "SelectionChanged event was not raised");
ValidateSelection(selectionModel, Path(1, 1), Path(1, 2), Path(1, 3));
_output.WriteLine("Removing 1.0");
selectionChangedRaised = false;
(data[1] as AvaloniaList<object>).RemoveAt(0);
Assert.True(selectionChangedRaised, "SelectionChanged event was not raised");
ValidateSelection(selectionModel,
Path(1, 0),
Path(1, 1),
Path(1, 2));
}
[Fact]
public void ValidatePropertyChangedEventIsRaised()
{
var selectionModel = new SelectionModel();
_output.WriteLine("Set the source to 10 items");
selectionModel.Source = Enumerable.Range(0, 10).ToList();
bool selectedIndexChanged = false;
bool selectedIndicesChanged = false;
bool SelectedItemChanged = false;
bool SelectedItemsChanged = false;
bool AnchorIndexChanged = false;
selectionModel.PropertyChanged += (sender, args) =>
{
switch (args.PropertyName)
{
case "SelectedIndex":
selectedIndexChanged = true;
break;
case "SelectedIndices":
selectedIndicesChanged = true;
break;
case "SelectedItem":
SelectedItemChanged = true;
break;
case "SelectedItems":
SelectedItemsChanged = true;
break;
case "AnchorIndex":
AnchorIndexChanged = true;
break;
default:
throw new InvalidOperationException();
}
};
Select(selectionModel, 3, true);
Assert.True(selectedIndexChanged);
Assert.True(selectedIndicesChanged);
Assert.True(SelectedItemChanged);
Assert.True(SelectedItemsChanged);
Assert.True(AnchorIndexChanged);
}
[Fact]
public void CanExtendSelectionModelINPC()
{
var selectionModel = new CustomSelectionModel();
bool intPropertyChanged = false;
selectionModel.PropertyChanged += (sender, args) =>
{
if (args.PropertyName == "IntProperty")
{
intPropertyChanged = true;
}
};
selectionModel.IntProperty = 5;
Assert.True(intPropertyChanged);
}
[Fact]
public void SelectRangeRegressionTest()
{
var selectionModel = new SelectionModel()
{
Source = CreateNestedData(1, 2, 3)
};
// length of start smaller than end used to cause an out of range error.
selectionModel.SelectRange(IndexPath.CreateFrom(0), IndexPath.CreateFrom(1, 1));
ValidateSelection(selectionModel,
Path(0),
Path(1),
Path(0, 0),
Path(0, 1),
Path(0, 2),
Path(1, 0),
Path(1, 1));
}
[Fact]
public void SelectRange_Should_Select_Nested_Items_On_Different_Levels()
{
var target = new SelectionModel();
var data = CreateNestedData(1, 2, 3);
target.Source = data;
target.AnchorIndex = new IndexPath(0, 1);
target.SelectRange(Path(0, 1), Path(1));
Assert.Equal(
new[]
{
Path(1),
Path(0, 1),
Path(0, 2),
},
target.SelectedIndices);
}
[Fact]
public void Should_Listen_For_Changes_After_Deselect()
{
var target = new SelectionModel();
var data = CreateNestedData(1, 2, 3);
target.Source = data;
target.Select(1, 0);
target.Deselect(1, 0);
target.Select(1, 0);
((AvaloniaList<object>)data[1]).Insert(0, "foo");
Assert.Equal(new IndexPath(1, 1), target.SelectedIndex);
}
[Fact]
public void Selecting_Item_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(4) }, e.SelectedIndices);
Assert.Equal(new object[] { 4 }, e.SelectedItems);
++raised;
};
target.Select(4);
Assert.Equal(1, raised);
}
[Fact]
public void Selecting_Already_Selected_Item_Doesnt_Raise_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(4);
target.SelectionChanged += (s, e) => ++raised;
target.Select(4);
Assert.Equal(0, raised);
}
[Fact]
public void SingleSelecting_Item_Raises_SelectionChanged()
{
var target = new SelectionModel { SingleSelect = true };
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(3);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(3) }, e.DeselectedIndices);
Assert.Equal(new object[] { 3 }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(4) }, e.SelectedIndices);
Assert.Equal(new object[] { 4 }, e.SelectedItems);
++raised;
};
target.Select(4);
Assert.Equal(1, raised);
}
[Fact]
public void SingleSelecting_Already_Selected_Item_Doesnt_Raise_SelectionChanged()
{
var target = new SelectionModel { SingleSelect = true };
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(4);
target.SelectionChanged += (s, e) => ++raised;
target.Select(4);
Assert.Equal(0, raised);
}
[Fact]
public void Selecting_Item_With_Group_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = CreateNestedData(1, 2, 3);
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(1, 1) }, e.SelectedIndices);
Assert.Equal(new object[] { 4 }, e.SelectedItems);
++raised;
};
target.Select(1, 1);
Assert.Equal(1, raised);
}
[Fact]
public void SelectAt_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = CreateNestedData(1, 2, 3);
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(1, 1) }, e.SelectedIndices);
Assert.Equal(new object[] { 4 }, e.SelectedItems);
++raised;
};
target.SelectAt(new IndexPath(1, 1));
Assert.Equal(1, raised);
}
[Fact]
public void SelectAll_Raises_SelectionChanged()
{
var target = new SelectionModel { SingleSelect = true };
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(0, 10);
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(expected.Select(x => new IndexPath(x)), e.SelectedIndices);
Assert.Equal(expected, e.SelectedItems.Cast<int>());
++raised;
};
target.SelectAll();
Assert.Equal(1, raised);
}
[Fact]
public void SelectAll_With_Already_Selected_Items_Raises_SelectionChanged()
{
var target = new SelectionModel { SingleSelect = true };
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(4);
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(0, 10).Except(new[] { 4 });
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(expected.Select(x => new IndexPath(x)), e.SelectedIndices);
Assert.Equal(expected, e.SelectedItems.Cast<int>());
++raised;
};
target.SelectAll();
Assert.Equal(1, raised);
}
[Fact]
public void SelectRangeFromAnchor_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(4, 3);
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(expected.Select(x => new IndexPath(x)), e.SelectedIndices);
Assert.Equal(expected, e.SelectedItems.Cast<int>());
++raised;
};
target.AnchorIndex = new IndexPath(4);
target.SelectRangeFromAnchor(6);
Assert.Equal(1, raised);
}
[Fact]
public void SelectRangeFromAnchor_With_Group_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = CreateNestedData(1, 2, 10);
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(11, 6);
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(expected.Select(x => new IndexPath(x / 10, x % 10)), e.SelectedIndices);
Assert.Equal(expected, e.SelectedItems.Cast<int>());
++raised;
};
target.AnchorIndex = new IndexPath(1, 1);
target.SelectRangeFromAnchor(1, 6);
Assert.Equal(1, raised);
}
[Fact]
public void SelectRangeFromAnchorTo_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = CreateNestedData(1, 2, 10);
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(11, 6);
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(expected.Select(x => new IndexPath(x / 10, x % 10)), e.SelectedIndices);
Assert.Equal(expected, e.SelectedItems.Cast<int>());
++raised;
};
target.AnchorIndex = new IndexPath(1, 1);
target.SelectRangeFromAnchorTo(new IndexPath(1, 6));
Assert.Equal(1, raised);
}
[Fact]
public void ClearSelection_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(4);
target.Select(5);
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(4, 2);
Assert.Equal(expected.Select(x => new IndexPath(x)), e.DeselectedIndices);
Assert.Equal(expected, e.DeselectedItems.Cast<int>());
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
target.ClearSelection();
Assert.Equal(1, raised);
}
[Fact]
public void Clearing_Nested_Selection_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = CreateNestedData(1, 2, 3);
target.Select(1, 1);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(1, 1) }, e.DeselectedIndices);
Assert.Equal(new object[] { 4 }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
target.ClearSelection();
Assert.Equal(1, raised);
}
[Fact]
public void Changing_Source_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(4);
target.Select(5);
target.SelectionChanged += (s, e) =>
{
var expected = Enumerable.Range(4, 2);
Assert.Equal(expected.Select(x => new IndexPath(x)), e.DeselectedIndices);
Assert.Equal(expected, e.DeselectedItems.Cast<int>());
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
target.Source = Enumerable.Range(20, 10).ToList();
Assert.Equal(1, raised);
}
[Fact]
public void Setting_SelectedIndex_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(4);
target.Select(5);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(4), new IndexPath(5) }, e.DeselectedIndices);
Assert.Equal(new object[] { 4, 5 }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(6) }, e.SelectedIndices);
Assert.Equal(new object[] { 6 }, e.SelectedItems);
++raised;
};
target.SelectedIndex = new IndexPath(6);
Assert.Equal(1, raised);
}
[Fact]
public void Removing_Selected_Item_Raises_SelectionChanged()
{
var target = new SelectionModel();
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var raised = 0;
target.Source = data;
target.Select(4);
target.Select(5);
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new object[] { 4 }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
data.Remove(4);
Assert.Equal(1, raised);
}
[Fact]
public void Removing_Selected_Child_Item_Raises_SelectionChanged()
{
var target = new SelectionModel();
var data = CreateNestedData(1, 2, 3);
var raised = 0;
target.Source = data;
target.SelectRange(new IndexPath(0), new IndexPath(1, 1));
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new object[] { 1}, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
((AvaloniaList<object>)data[0]).RemoveAt(1);
Assert.Equal(1, raised);
}
[Fact]
public void Removing_Selected_Item_With_Children_Raises_SelectionChanged()
{
var target = new SelectionModel();
var data = CreateNestedData(1, 2, 3);
var raised = 0;
target.Source = data;
target.SelectRange(new IndexPath(0), new IndexPath(1, 1));
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new object[] { new AvaloniaList<int> { 0, 1, 2 }, 0, 1, 2 }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
data.RemoveAt(0);
Assert.Equal(1, raised);
}
[Fact]
public void Removing_Unselected_Item_Before_Selected_Item_Raises_SelectionChanged()
{
var target = new SelectionModel();
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var raised = 0;
target.Source = data;
target.Select(8);
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
data.Remove(6);
Assert.Equal(1, raised);
}
[Fact]
public void Removing_Unselected_Item_After_Selected_Item_Doesnt_Raise_SelectionChanged()
{
var target = new SelectionModel();
var data = new ObservableCollection<int>(Enumerable.Range(0, 10));
var raised = 0;
target.Source = data;
target.Select(4);
target.SelectionChanged += (s, e) => ++raised;
data.Remove(6);
Assert.Equal(0, raised);
}
[Fact]
public void Disposing_Unhooks_CollectionChanged_Handlers()
{
var data = CreateNestedData(2, 2, 2);
var target = new SelectionModel { Source = data };
target.SelectAll();
VerifyCollectionChangedHandlers(1, data);
target.Dispose();
VerifyCollectionChangedHandlers(0, data);
}
[Fact]
public void Clearing_Selection_Unhooks_CollectionChanged_Handlers()
{
var data = CreateNestedData(2, 2, 2);
var target = new SelectionModel { Source = data };
target.SelectAll();
VerifyCollectionChangedHandlers(1, data);
target.ClearSelection();
// Root subscription not unhooked until SelectionModel is disposed.
Assert.Equal(1, GetSubscriberCount(data));
foreach (AvaloniaList<object> i in data)
{
VerifyCollectionChangedHandlers(0, i);
}
}
[Fact]
public void Removing_Item_Unhooks_CollectionChanged_Handlers()
{
var data = CreateNestedData(2, 2, 2);
var target = new SelectionModel { Source = data };
target.SelectAll();
var toRemove = (AvaloniaList<object>)data[1];
data.Remove(toRemove);
Assert.Equal(0, GetSubscriberCount(toRemove));
}
[Fact]
public void SelectRange_Behaves_The_Same_As_Multiple_Selects()
{
var data = new[] { 1, 2, 3 };
var target = new SelectionModel { Source = data };
target.Select(1);
Assert.Equal(new[] { IndexPath.CreateFrom(1) }, target.SelectedIndices);
target.ClearSelection();
target.SelectRange(new IndexPath(1), new IndexPath(1));
Assert.Equal(new[] { IndexPath.CreateFrom(1) }, target.SelectedIndices);
}
[Fact]
public void SelectRange_Behaves_The_Same_As_Multiple_Selects_Nested()
{
var data = CreateNestedData(3, 2, 2);
var target = new SelectionModel { Source = data };
target.Select(1);
Assert.Equal(new[] { IndexPath.CreateFrom(1) }, target.SelectedIndices);
target.ClearSelection();
target.SelectRange(new IndexPath(1), new IndexPath(1));
Assert.Equal(new[] { IndexPath.CreateFrom(1) }, target.SelectedIndices);
}
[Fact]
public void Should_Not_Treat_Strings_As_Nested_Selections()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
target.SelectAll();
Assert.Equal(3, target.SelectedItems.Count);
}
[Fact]
public void Not_Enumerating_Changes_Does_Not_Prevent_Further_Operations()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
target.SelectionChanged += (s, e) => { };
target.SelectAll();
target.ClearSelection();
}
[Fact]
public void Can_Change_Selection_From_SelectionChanged()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
if (raised++ == 0)
{
target.ClearSelection();
}
};
target.SelectAll();
Assert.Equal(2, raised);
}
[Fact]
public void Raises_SelectionChanged_With_No_Source()
{
var target = new SelectionModel();
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(1) }, e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
target.Select(1);
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Empty(target.SelectedItems);
}
[Fact]
public void Raises_SelectionChanged_With_Items_After_Source_Is_Set()
{
var target = new SelectionModel();
var raised = 0;
target.Select(1);
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(1) }, e.SelectedIndices);
Assert.Equal(new[] { "bar" }, e.SelectedItems);
++raised;
};
target.Source = new[] { "foo", "bar", "baz" };
Assert.Equal(1, raised);
}
[Fact]
public void RetainSelectionOnReset_Retains_Selection_On_Reset()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
target.SelectRange(new IndexPath(1), new IndexPath(2));
data.Reset();
Assert.Equal(new[] { new IndexPath(1), new IndexPath(2) }, target.SelectedIndices);
Assert.Equal(new[] { "bar", "baz" }, target.SelectedItems);
}
[Fact]
public void RetainSelectionOnReset_Retains_Correct_Selection_After_Deselect()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
target.SelectRange(new IndexPath(1), new IndexPath(2));
target.Deselect(2);
data.Reset();
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
}
[Fact]
public void RetainSelectionOnReset_Retains_Correct_Selection_After_Remove_1()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
target.SelectRange(new IndexPath(1), new IndexPath(2));
data.RemoveAt(2);
data.Reset(new[] { "foo", "bar", "baz" });
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal(new[] { "bar" }, target.SelectedItems);
}
[Fact]
public void RetainSelectionOnReset_Retains_Correct_Selection_After_Remove_2()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
target.SelectRange(new IndexPath(1), new IndexPath(2));
data.RemoveAt(0);
data.Reset(new[] { "foo", "bar", "baz" });
Assert.Equal(new[] { new IndexPath(1), new IndexPath(2) }, target.SelectedIndices);
Assert.Equal(new[] { "bar", "baz" }, target.SelectedItems);
}
[Fact]
public void RetainSelectionOnReset_Retains_No_Selection_After_Clear()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
target.SelectRange(new IndexPath(1), new IndexPath(2));
target.ClearSelection();
data.Reset();
Assert.Empty(target.SelectedIndices);
Assert.Empty(target.SelectedItems);
}
[Fact]
public void RetainSelectionOnReset_Retains_Correct_Selection_After_Two_Resets()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
target.SelectRange(new IndexPath(1), new IndexPath(2));
data.Reset(new[] { "foo", "bar" });
data.Reset(new[] { "foo", "bar", "baz" });
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
Assert.Equal(new[] { "bar", }, target.SelectedItems);
}
[Fact]
public void RetainSelectionOnReset_Raises_Empty_SelectionChanged_On_Reset_With_No_Changes()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
var raised = 0;
target.SelectRange(new IndexPath(1), new IndexPath(2));
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
data.Reset();
}
[Fact]
public void RetainSelectionOnReset_Raises_SelectionChanged_On_Reset_With_Removed_Items()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data, RetainSelectionOnReset = true };
var raised = 0;
target.SelectRange(new IndexPath(1), new IndexPath(2));
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new[] { "bar" }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
data.Reset(new[] { "foo", "baz" });
Assert.Equal(1, raised);
}
[Fact]
public void RetainSelectionOnReset_Handles_Null_Source()
{
var data = new ResettingList<string> { "foo", "bar", "baz" };
var target = new SelectionModel { RetainSelectionOnReset = true };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(1) }, e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
else if (raised == 1)
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(1) }, e.SelectedIndices);
Assert.Equal(new[] { "bar" }, e.SelectedItems);
}
else if (raised == 3)
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
++raised;
};
target.Select(1);
Assert.Equal(1, raised);
target.Source = data;
Assert.Equal(2, raised);
Assert.Equal(new[] { new IndexPath(1) }, target.SelectedIndices);
data.Reset(new[] { "qux", "foo", "bar", "baz" });
Assert.Equal(3, raised);
Assert.Equal(new[] { new IndexPath(2) }, target.SelectedIndices);
}
[Fact]
public void Can_Batch_Update()
{
var target = new SelectionModel();
var raised = 0;
target.Source = Enumerable.Range(0, 10).ToList();
target.Select(1);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(1) }, e.DeselectedIndices);
Assert.Equal(new object[] { 1 }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(4) }, e.SelectedIndices);
Assert.Equal(new object[] { 4 }, e.SelectedItems);
++raised;
};
using (target.Update())
{
target.Deselect(1);
target.Select(4);
}
Assert.Equal(1, raised);
}
[Fact]
public void Batch_Update_Clear_Nested_Data_Raises_SelectionChanged()
{
var target = new SelectionModel();
var raised = 0;
target.Source = CreateNestedData(3, 2, 2);
target.SelectRange(new IndexPath(0), new IndexPath(1, 1));
Assert.Equal(24, target.SelectedIndices.Count);
var indices = target.SelectedIndices.ToList();
var items = target.SelectedItems.ToList();
target.SelectionChanged += (s, e) =>
{
Assert.Equal(indices, e.DeselectedIndices);
Assert.Equal(items, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
using (target.Update())
{
target.ClearSelection();
}
Assert.Equal(1, raised);
}
[Fact]
public void Batch_Update_Does_Not_Raise_PropertyChanged_Until_Operation_Finished()
{
var data = new[] { "foo", "bar", "baz", "qux" };
var target = new SelectionModel { Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(1);
Assert.Equal(new IndexPath(1), target.AnchorIndex);
target.PropertyChanged += (s, e) => ++raised;
using (target.Update())
{
target.ClearSelection();
Assert.Equal(0, raised);
target.AnchorIndex = new IndexPath(2);
Assert.Equal(0, raised);
target.SelectedIndex = new IndexPath(3);
Assert.Equal(0, raised);
}
Assert.Equal(new IndexPath(3), target.AnchorIndex);
Assert.Equal(5, raised);
}
[Fact]
public void Batch_Update_Does_Not_Raise_PropertyChanged_If_Nothing_Changed()
{
var data = new[] { "foo", "bar", "baz", "qux" };
var target = new SelectionModel { Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(1);
Assert.Equal(new IndexPath(1), target.AnchorIndex);
target.PropertyChanged += (s, e) => ++raised;
using (target.Update())
{
target.ClearSelection();
target.SelectedIndex = new IndexPath(1);
}
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()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.AutoSelect = true;
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_When_Source_Assigned()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.Source = data;
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_When_New_Source_Assigned_And_Old_Source_Has_Selection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Equal(new[] { new IndexPath(0) }, e.DeselectedIndices);
Assert.Equal(new[] { "foo" }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
else
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "newfoo" }, e.SelectedItems);
}
++raised;
};
target.Source = new[] { "newfoo" };
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(2, raised);
}
[Fact]
public void AutoSelect_Selects_When_First_Item_Added()
{
var data = new ObservableCollection<string>();
var target = new SelectionModel { AutoSelect = true , Source = data };
var raised = 0;
target.SelectionChanged += (s, e) =>
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
data.Add("foo");
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_When_Selected_Item_Removed()
{
var data = new ObservableCollection<string> { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(2);
target.SelectionChanged += (s, e) =>
{
if (raised == 0)
{
Assert.Empty(e.DeselectedIndices);
Assert.Equal(new[] { "baz" }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
}
else
{
Assert.Empty(e.DeselectedIndices);
Assert.Empty(e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
}
++raised;
};
data.RemoveAt(2);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(2, raised);
}
[Fact]
public void AutoSelect_Selects_On_Deselection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(2);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(2) }, e.DeselectedIndices);
Assert.Equal(new[] { "baz" }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.Deselect(2);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Selects_On_ClearSelection()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.SelectedIndex = new IndexPath(2);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { new IndexPath(2) }, e.DeselectedIndices);
Assert.Equal(new[] { "baz" }, e.DeselectedItems);
Assert.Equal(new[] { new IndexPath(0) }, e.SelectedIndices);
Assert.Equal(new[] { "foo" }, e.SelectedItems);
++raised;
};
target.ClearSelection();
Assert.Equal(new IndexPath(0), target.SelectedIndex);
Assert.Equal(1, raised);
}
[Fact]
public void AutoSelect_Overrides_Deselecting_First_Item()
{
var data = new[] { "foo", "bar", "baz" };
var target = new SelectionModel { AutoSelect = true, Source = data };
var raised = 0;
target.Select(0);
target.SelectionChanged += (s, e) =>
{
++raised;
};
target.Deselect(0);
Assert.Equal(new IndexPath(0), target.SelectedIndex);
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()
{
var root = new Node("Root");
var target = new SelectionModel { Source = new[] { root } };
var raised = 0;
target.ChildrenRequested += (s, e) => e.Children = ((Node)e.Source).WhenAnyValue(x => x.Children);
target.Select(0, 9);
var selected = (Node)target.SelectedItem;
Assert.Equal("Child 9", selected.Header);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { Path(0, 9) }, e.DeselectedIndices);
Assert.Equal(new[] { selected }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
root.ReplaceChildren();
Assert.Null(target.SelectedItem);
Assert.Equal(1, raised);
}
[Fact]
public void Can_Replace_Grandparent_Children_Collection()
{
var root = new Node("Root");
var target = new SelectionModel { Source = new[] { root } };
var raised = 0;
target.ChildrenRequested += (s, e) => e.Children = ((Node)e.Source).WhenAnyValue(x => x.Children);
target.SelectAt(Path(0, 9, 1));
var selected = (Node)target.SelectedItem;
Assert.Equal("Child 1", selected.Header);
target.SelectionChanged += (s, e) =>
{
Assert.Equal(new[] { Path(0, 9, 1) }, e.DeselectedIndices);
Assert.Equal(new[] { selected }, e.DeselectedItems);
Assert.Empty(e.SelectedIndices);
Assert.Empty(e.SelectedItems);
++raised;
};
root.ReplaceChildren();
Assert.Null(target.SelectedItem);
Assert.Equal(1, raised);
}
[Fact]
public void Child_Resolver_Is_Unsubscribed_When_Source_Changed()
{
var root = new Node("Root");
var target = new SelectionModel { Source = new[] { root } };
target.ChildrenRequested += (s, e) => e.Children = ((Node)e.Source).WhenAnyValue(x => x.Children);
target.Select(0, 9);
Assert.Equal(1, root.PropertyChangedSubscriptions);
target.Source = null;
Assert.Equal(0, root.PropertyChangedSubscriptions);
}
[Fact]
public void Child_Resolver_Is_Unsubscribed_When_Parent_Removed()
{
var root = new Node("Root");
var target = new SelectionModel { Source = new[] { root } };
var node = root.Children[1];
var path = new IndexPath(new[] { 0, 1, 1 });
target.ChildrenRequested += (s, e) => e.Children = ((Node)e.Source).WhenAnyValue(x => x.Children);
target.SelectAt(path);
Assert.Equal(1, node.PropertyChangedSubscriptions);
root.ReplaceChildren();
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;
}
private void VerifyCollectionChangedHandlers(int expected, AvaloniaList<object> list)
{
var count = GetSubscriberCount(list);
Assert.Equal(expected, count);
foreach (var i in list)
{
if (i is AvaloniaList<object> l)
{
VerifyCollectionChangedHandlers(expected, l);
}
}
}
private void Select(SelectionModel manager, int index, bool select)
{
_output.WriteLine((select ? "Selecting " : "DeSelecting ") + index);
if (select)
{
manager.Select(index);
}
else
{
manager.Deselect(index);
}
}
private void Select(SelectionModel manager, int groupIndex, int itemIndex, bool select)
{
_output.WriteLine((select ? "Selecting " : "DeSelecting ") + groupIndex + "." + itemIndex);
if (select)
{
manager.Select(groupIndex, itemIndex);
}
else
{
manager.Deselect(groupIndex, itemIndex);
}
}
private void Select(SelectionModel manager, IndexPath index, bool select)
{
_output.WriteLine((select ? "Selecting " : "DeSelecting ") + index);
if (select)
{
manager.SelectAt(index);
}
else
{
manager.DeselectAt(index);
}
}
private void SelectRangeFromAnchor(SelectionModel manager, int index, bool select)
{
_output.WriteLine("SelectRangeFromAnchor " + index + " select: " + select.ToString());
if (select)
{
manager.SelectRangeFromAnchor(index);
}
else
{
manager.DeselectRangeFromAnchor(index);
}
}
private void SelectRangeFromAnchor(SelectionModel manager, int groupIndex, int itemIndex, bool select)
{
_output.WriteLine("SelectRangeFromAnchor " + groupIndex + "." + itemIndex + " select:" + select.ToString());
if (select)
{
manager.SelectRangeFromAnchor(groupIndex, itemIndex);
}
else
{
manager.DeselectRangeFromAnchor(groupIndex, itemIndex);
}
}
private void SelectRangeFromAnchor(SelectionModel manager, IndexPath index, bool select)
{
_output.WriteLine("SelectRangeFromAnchor " + index + " select: " + select.ToString());
if (select)
{
manager.SelectRangeFromAnchorTo(index);
}
else
{
manager.DeselectRangeFromAnchorTo(index);
}
}
private void ClearSelection(SelectionModel manager)
{
_output.WriteLine("ClearSelection");
manager.ClearSelection();
}
private void SetAnchorIndex(SelectionModel manager, int index)
{
_output.WriteLine("SetAnchorIndex " + index);
manager.SetAnchorIndex(index);
}
private void SetAnchorIndex(SelectionModel manager, int groupIndex, int itemIndex)
{
_output.WriteLine("SetAnchor " + groupIndex + "." + itemIndex);
manager.SetAnchorIndex(groupIndex, itemIndex);
}
private void SetAnchorIndex(SelectionModel manager, IndexPath index)
{
_output.WriteLine("SetAnchor " + index);
manager.AnchorIndex = index;
}
private void ValidateSelection(
SelectionModel selectionModel,
params IndexPath[] expectedSelected)
{
Assert.Equal(expectedSelected, selectionModel.SelectedIndices);
}
private object GetData(SelectionModel selectionModel, IndexPath indexPath)
{
var data = selectionModel.Source;
for (int i = 0; i < indexPath.GetSize(); i++)
{
var listData = data as IList;
data = listData[indexPath.GetAt(i)];
}
return data;
}
private bool AreEqual(IndexPath a, IndexPath b)
{
if (a.GetSize() != b.GetSize())
{
return false;
}
for (int i = 0; i < a.GetSize(); i++)
{
if (a.GetAt(i) != b.GetAt(i))
{
return false;
}
}
return true;
}
private List<IndexPath> GetIndexPathsInSource(object source)
{
List<IndexPath> paths = new List<IndexPath>();
Traverse(source, (TreeWalkNodeInfo node) =>
{
if (!paths.Contains(node.Path))
{
paths.Add(node.Path);
}
});
_output.WriteLine("All Paths in source..");
foreach (var path in paths)
{
_output.WriteLine(path.ToString());
}
_output.WriteLine("done.");
return paths;
}
private static void Traverse(object root, Action<TreeWalkNodeInfo> nodeAction)
{
var pendingNodes = new Stack<TreeWalkNodeInfo>();
IndexPath current = Path(null);
pendingNodes.Push(new TreeWalkNodeInfo() { Current = root, Path = current });
while (pendingNodes.Count > 0)
{
var currentNode = pendingNodes.Pop();
var currentObject = currentNode.Current as IList;
if (currentObject != null)
{
for (int i = currentObject.Count - 1; i >= 0; i--)
{
var child = currentObject[i];
List<int> path = new List<int>();
for (int idx = 0; idx < currentNode.Path.GetSize(); idx++)
{
path.Add(currentNode.Path.GetAt(idx));
}
path.Add(i);
var childPath = IndexPath.CreateFromIndices(path);
if (child != null)
{
pendingNodes.Push(new TreeWalkNodeInfo() { Current = child, Path = childPath });
}
}
}
nodeAction(currentNode);
}
}
private bool Contains(List<IndexPath> list, IndexPath index)
{
bool contains = false;
foreach (var item in list)
{
if (item.CompareTo(index) == 0)
{
contains = true;
break;
}
}
return contains;
}
public static AvaloniaList<object> CreateNestedData(int levels = 3, int groupsAtLevel = 5, int countAtLeaf = 10)
{
var nextData = 0;
return CreateNestedData(levels, groupsAtLevel, countAtLeaf, ref nextData);
}
public static AvaloniaList<object> CreateNestedData(
int levels,
int groupsAtLevel,
int countAtLeaf,
ref int nextData)
{
var data = new AvaloniaList<object>();
if (levels != 0)
{
for (int i = 0; i < groupsAtLevel; i++)
{
data.Add(CreateNestedData(levels - 1, groupsAtLevel, countAtLeaf, ref nextData));
}
}
else
{
for (int i = 0; i < countAtLeaf; i++)
{
data.Add(nextData++);
}
}
return data;
}
static IndexPath Path(params int[] path)
{
return IndexPath.CreateFromIndices(path);
}
private static int _nextData = 0;
private struct TreeWalkNodeInfo
{
public object Current { get; set; }
public IndexPath Path { get; set; }
}
private class ResettingList<T> : List<object>, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public new void RemoveAt(int index)
{
var item = this[index];
base.RemoveAt(index);
CollectionChanged?.Invoke(
this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index));
}
public void Reset(IEnumerable<object> items = null)
{
if (items != null)
{
Clear();
AddRange(items);
}
CollectionChanged?.Invoke(
this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private class Node : INotifyPropertyChanged
{
private ObservableCollection<Node> _children;
private PropertyChangedEventHandler _propertyChanged;
public Node(string header)
{
Header = header;
}
public string Header { get; }
public ObservableCollection<Node> Children
{
get => _children ??= CreateChildren(10);
private set
{
_children = value;
_propertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Children)));
}
}
public event PropertyChangedEventHandler PropertyChanged
{
add
{
_propertyChanged += value;
++PropertyChangedSubscriptions;
}
remove
{
_propertyChanged -= value;
--PropertyChangedSubscriptions;
}
}
public int PropertyChangedSubscriptions { get; private set; }
public void ReplaceChildren()
{
Children = CreateChildren(5);
}
private ObservableCollection<Node> CreateChildren(int count)
{
return new ObservableCollection<Node>(
Enumerable.Range(0, count).Select(x => new Node("Child " + x)));
}
}
}
class CustomSelectionModel : SelectionModel
{
public int IntProperty
{
get { return _intProperty; }
set
{
_intProperty = value;
OnPropertyChanged("IntProperty");
}
}
private int _intProperty;
}
}