From e4d45fc46d10a558d7193fe48e0a8362e6ad6d17 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 12 Feb 2020 08:33:00 +0100 Subject: [PATCH 1/2] ISelectionModel implements INotifyPropertyChanged. This will be needed for monitoring the `AnchorIndex` in order to auto-scroll. --- src/Avalonia.Controls/ISelectionModel.cs | 3 ++- src/Avalonia.Controls/SelectionModel.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ISelectionModel.cs b/src/Avalonia.Controls/ISelectionModel.cs index aed21315bb..a939bfdc8c 100644 --- a/src/Avalonia.Controls/ISelectionModel.cs +++ b/src/Avalonia.Controls/ISelectionModel.cs @@ -5,10 +5,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel; namespace Avalonia.Controls { - public interface ISelectionModel + public interface ISelectionModel : INotifyPropertyChanged { IndexPath AnchorIndex { get; set; } IndexPath SelectedIndex { get; set; } diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs index ce8e53c994..325e2e8848 100644 --- a/src/Avalonia.Controls/SelectionModel.cs +++ b/src/Avalonia.Controls/SelectionModel.cs @@ -12,7 +12,7 @@ using Avalonia.Controls.Utils; namespace Avalonia.Controls { - public class SelectionModel : ISelectionModel, INotifyPropertyChanged, IDisposable + public class SelectionModel : ISelectionModel, IDisposable { private readonly SelectionNode _rootNode; private bool _singleSelect; From 12abeab47e99efe667a94548d748eca1858c7d4b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 15 Feb 2020 09:47:54 +0100 Subject: [PATCH 2/2] Remove UWP stubs in unit tests. --- .../SelectionModelTests.cs | 1525 ++++++++--------- 1 file changed, 725 insertions(+), 800 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs b/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs index e1a22dd790..31e07d834a 100644 --- a/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/SelectionModelTests.cs @@ -17,248 +17,224 @@ namespace Avalonia.Controls.UnitTests { public class SelectionModelTests { - private LogWrapper Log { get; } + private readonly ITestOutputHelper _output; public SelectionModelTests(ITestOutputHelper output) { - Log = new LogWrapper(output); + _output = output; } [Fact] public void ValidateOneLevelSingleSelectionNoSource() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel() { SingleSelect = true }; - Log.Comment("No source set."); - Select(selectionModel, 4, true); - ValidateSelection(selectionModel, new List() { Path(4) }); - Select(selectionModel, 4, false); - ValidateSelection(selectionModel, new List() { }); - }); + SelectionModel selectionModel = new SelectionModel() { SingleSelect = true }; + _output.WriteLine("No source set."); + Select(selectionModel, 4, true); + ValidateSelection(selectionModel, new List() { Path(4) }); + Select(selectionModel, 4, false); + ValidateSelection(selectionModel, new List() { }); } [Fact] public void ValidateOneLevelSingleSelection() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel() { SingleSelect = true }; - Log.Comment("Set the source to 10 items"); - selectionModel.Source = Enumerable.Range(0, 10).ToList(); - Select(selectionModel, 3, true); - ValidateSelection(selectionModel, new List() { Path(3) }, new List() { Path() }); - Select(selectionModel, 3, false); - ValidateSelection(selectionModel, new List() { }); - }); + 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, new List() { Path(3) }, new List() { Path() }); + Select(selectionModel, 3, false); + ValidateSelection(selectionModel, new List() { }); } [Fact] public void ValidateSelectionChangedEvent() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel(); - selectionModel.Source = Enumerable.Range(0, 10).ToList(); - - int selectionChangedFiredCount = 0; - selectionModel.SelectionChanged += delegate (object sender, SelectionModelSelectionChangedEventArgs args) - { - selectionChangedFiredCount++; - ValidateSelection(selectionModel, new List() { Path(4) }, new List() { Path() }); - }; + SelectionModel selectionModel = new SelectionModel(); + selectionModel.Source = Enumerable.Range(0, 10).ToList(); - Select(selectionModel, 4, true); + int selectionChangedFiredCount = 0; + selectionModel.SelectionChanged += delegate (object sender, SelectionModelSelectionChangedEventArgs args) + { + selectionChangedFiredCount++; ValidateSelection(selectionModel, new List() { Path(4) }, new List() { Path() }); - Assert.Equal(1, selectionChangedFiredCount); - }); + }; + + Select(selectionModel, 4, true); + ValidateSelection(selectionModel, new List() { Path(4) }, new List() { Path() }); + Assert.Equal(1, selectionChangedFiredCount); } [Fact] public void ValidateCanSetSelectedIndex() { - RunOnUIThread.Execute(() => - { - var model = new SelectionModel(); - var ip = IndexPath.CreateFrom(34); - model.SelectedIndex = ip; - Assert.Equal(0, ip.CompareTo(model.SelectedIndex)); - }); + var model = new SelectionModel(); + var ip = IndexPath.CreateFrom(34); + model.SelectedIndex = ip; + Assert.Equal(0, ip.CompareTo(model.SelectedIndex)); } [Fact] public void ValidateOneLevelMultipleSelection() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel(); - selectionModel.Source = Enumerable.Range(0, 10).ToList(); - - Select(selectionModel, 4, true); - ValidateSelection(selectionModel, new List() { Path(4) }, new List() { Path() }); - SelectRangeFromAnchor(selectionModel, 8, true /* select */); - ValidateSelection(selectionModel, - new List() - { - Path(4), - Path(5), - Path(6), - Path(7), - Path(8) - }, - new List() { Path() }); - - ClearSelection(selectionModel); - SetAnchorIndex(selectionModel, 6); - SelectRangeFromAnchor(selectionModel, 3, true /* select */); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(4), - Path(5), - Path(6) - }, - new List() { Path() }); - - SetAnchorIndex(selectionModel, 4); - SelectRangeFromAnchor(selectionModel, 5, false /* select */); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(6) - }, - new List() { Path() }); - }); + SelectionModel selectionModel = new SelectionModel(); + selectionModel.Source = Enumerable.Range(0, 10).ToList(); + + Select(selectionModel, 4, true); + ValidateSelection(selectionModel, new List() { Path(4) }, new List() { Path() }); + SelectRangeFromAnchor(selectionModel, 8, true /* select */); + ValidateSelection(selectionModel, + new List() + { + Path(4), + Path(5), + Path(6), + Path(7), + Path(8) + }, + new List() { Path() }); + + ClearSelection(selectionModel); + SetAnchorIndex(selectionModel, 6); + SelectRangeFromAnchor(selectionModel, 3, true /* select */); + ValidateSelection(selectionModel, + new List() + { + Path(3), + Path(4), + Path(5), + Path(6) + }, + new List() { Path() }); + + SetAnchorIndex(selectionModel, 4); + SelectRangeFromAnchor(selectionModel, 5, false /* select */); + ValidateSelection(selectionModel, + new List() + { + Path(3), + Path(6) + }, + new List() { Path() }); } [Fact] public void ValidateTwoLevelSingleSelection() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel(); - Log.Comment("Setting the source"); - selectionModel.Source = CreateNestedData(1 /* levels */ , 2 /* groupsAtLevel */, 2 /* countAtLeaf */); - Select(selectionModel, 1, 1, true); - ValidateSelection(selectionModel, - new List() { Path(1, 1) }, new List() { Path(), Path(1) }); - Select(selectionModel, 1, 1, false); - ValidateSelection(selectionModel, new List() { }); - }); + 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, + new List() { Path(1, 1) }, new List() { Path(), Path(1) }); + Select(selectionModel, 1, 1, false); + ValidateSelection(selectionModel, new List() { }); } [Fact] public void ValidateTwoLevelMultipleSelection() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel(); - Log.Comment("Setting the source"); - selectionModel.Source = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); - - Select(selectionModel, 1, 2, true); - ValidateSelection(selectionModel, new List() { Path(1, 2) }, new List() { Path(), Path(1) }); - SelectRangeFromAnchor(selectionModel, 2, 2, true /* select */); - ValidateSelection(selectionModel, - new List() - { - Path(1, 2), - Path(2), // Inner node should be selected since everything 2.* is selected - Path(2, 0), - Path(2, 1), - Path(2, 2) - }, - new List() - { - Path(), - Path(1) - }, - 1 /* selectedInnerNodes */); - - ClearSelection(selectionModel); - SetAnchorIndex(selectionModel, 2, 1); - SelectRangeFromAnchor(selectionModel, 0, 1, true /* select */); - ValidateSelection(selectionModel, - new List() - { - Path(0, 1), - Path(0, 2), - Path(1, 0), - Path(1, 1), - Path(1, 2), - Path(1), - Path(2, 0), - Path(2, 1) - }, - new List() - { - Path(), - Path(0), - Path(2), - }, - 1 /* selectedInnerNodes */); - - SetAnchorIndex(selectionModel, 1, 1); - SelectRangeFromAnchor(selectionModel, 2, 0, false /* select */); - ValidateSelection(selectionModel, - new List() - { - Path(0, 1), - Path(0, 2), - Path(1, 0), - Path(2, 1) - }, - new List() - { - Path(), - Path(1), - Path(0), - Path(2), - }, - 0 /* selectedInnerNodes */); - - ClearSelection(selectionModel); - ValidateSelection(selectionModel, new List() { }); - }); + 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, new List() { Path(1, 2) }, new List() { Path(), Path(1) }); + SelectRangeFromAnchor(selectionModel, 2, 2, true /* select */); + ValidateSelection(selectionModel, + new List() + { + Path(1, 2), + Path(2), // Inner node should be selected since everything 2.* is selected + Path(2, 0), + Path(2, 1), + Path(2, 2) + }, + new List() + { + Path(), + Path(1) + }, + 1 /* selectedInnerNodes */); + + ClearSelection(selectionModel); + SetAnchorIndex(selectionModel, 2, 1); + SelectRangeFromAnchor(selectionModel, 0, 1, true /* select */); + ValidateSelection(selectionModel, + new List() + { + Path(0, 1), + Path(0, 2), + Path(1, 0), + Path(1, 1), + Path(1, 2), + Path(1), + Path(2, 0), + Path(2, 1) + }, + new List() + { + Path(), + Path(0), + Path(2), + }, + 1 /* selectedInnerNodes */); + + SetAnchorIndex(selectionModel, 1, 1); + SelectRangeFromAnchor(selectionModel, 2, 0, false /* select */); + ValidateSelection(selectionModel, + new List() + { + Path(0, 1), + Path(0, 2), + Path(1, 0), + Path(2, 1) + }, + new List() + { + Path(), + Path(1), + Path(0), + Path(2), + }, + 0 /* selectedInnerNodes */); + + ClearSelection(selectionModel); + ValidateSelection(selectionModel, new List() { }); } [Fact] public void ValidateNestedSingleSelection() { - RunOnUIThread.Execute(() => - { - SelectionModel selectionModel = new SelectionModel() { SingleSelect = true }; - Log.Comment("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, - new List() { path }, - new List() - { - Path(), - Path(1), - Path(1, 0), - Path(1, 0, 1), - }); - Select(selectionModel, Path(0, 0, 1, 0), true); - ValidateSelection(selectionModel, - new List() - { - Path(0, 0, 1, 0) - }, - new List() - { - Path(), - Path(0), - Path(0, 0), - Path(0, 0, 1) - }); - Select(selectionModel, Path(0, 0, 1, 0), false); - ValidateSelection(selectionModel, new List() { }); - }); + 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, + new List() { path }, + new List() + { + Path(), + Path(1), + Path(1, 0), + Path(1, 0, 1), + }); + Select(selectionModel, Path(0, 0, 1, 0), true); + ValidateSelection(selectionModel, + new List() + { + Path(0, 0, 1, 0) + }, + new List() + { + Path(), + Path(0), + Path(0, 0), + Path(0, 0, 1) + }); + Select(selectionModel, Path(0, 0, 1, 0), false); + ValidateSelection(selectionModel, new List() { }); } [Theory] @@ -266,497 +242,470 @@ namespace Avalonia.Controls.UnitTests [InlineData(false)] public void ValidateNestedMultipleSelection(bool handleChildrenRequested) { - RunOnUIThread.Execute(() => + SelectionModel selectionModel = new SelectionModel(); + List sourcePaths = new List(); + + _output.WriteLine("Setting the source"); + selectionModel.Source = CreateNestedData(3 /* levels */ , 2 /* groupsAtLevel */, 4 /* countAtLeaf */); + if (handleChildrenRequested) { - SelectionModel selectionModel = new SelectionModel(); - List sourcePaths = new List(); + selectionModel.ChildrenRequested += (object sender, SelectionModelChildrenRequestedEventArgs args) => + { + _output.WriteLine("ChildrenRequestedIndexPath:" + args.SourceIndex); + sourcePaths.Add(args.SourceIndex); + args.Children = args.Source is IEnumerable ? args.Source : null; + }; + } - Log.Comment("Setting the source"); - selectionModel.Source = CreateNestedData(3 /* levels */ , 2 /* groupsAtLevel */, 4 /* countAtLeaf */); - if (handleChildrenRequested) + var startPath = Path(1, 0, 1, 0); + Select(selectionModel, startPath, true); + ValidateSelection(selectionModel, + new List() { startPath }, + new List() { - selectionModel.ChildrenRequested += (object sender, SelectionModelChildrenRequestedEventArgs args) => - { - Log.Comment("ChildrenRequestedIndexPath:" + args.SourceIndex); - sourcePaths.Add(args.SourceIndex); - args.Children = args.Source is IEnumerable ? args.Source : null; - }; - } + Path(), + Path(1), + Path(1, 0), + Path(1, 0, 1) + }); - var startPath = Path(1, 0, 1, 0); - Select(selectionModel, startPath, true); - ValidateSelection(selectionModel, - new List() { startPath }, - new List() - { - Path(), - Path(1), - Path(1, 0), - Path(1, 0, 1) - }); + var endPath = Path(1, 1, 1, 0); + SelectRangeFromAnchor(selectionModel, endPath, true /* select */); - var endPath = Path(1, 1, 1, 0); - SelectRangeFromAnchor(selectionModel, endPath, true /* select */); + if (handleChildrenRequested) + { + // Validate SourceIndices. + var expectedSourceIndices = new List() + { + Path(), + Path(1), + Path(1, 0), + Path(1), + Path(1, 0, 1), + Path(1, 0, 1), + Path(1, 0, 1), + Path(1, 0, 1), + Path(1, 1), + Path(1, 1), + Path(1, 1, 0), + Path(1, 1, 0), + Path(1, 1, 0), + Path(1, 1, 0), + Path(1, 1, 1) + }; - if (handleChildrenRequested) + Assert.Equal(expectedSourceIndices.Count, sourcePaths.Count); + for (int i = 0; i < expectedSourceIndices.Count; i++) { - // Validate SourceIndices. - var expectedSourceIndices = new List() - { - Path(), - Path(1), - Path(1, 0), - Path(1), - Path(1, 0, 1), - Path(1, 0, 1), - Path(1, 0, 1), - Path(1, 0, 1), - Path(1, 1), - Path(1, 1), - Path(1, 1, 0), - Path(1, 1, 0), - Path(1, 1, 0), - Path(1, 1, 0), - Path(1, 1, 1) - }; - - Assert.Equal(expectedSourceIndices.Count, sourcePaths.Count); - for (int i = 0; i < expectedSourceIndices.Count; i++) - { - Assert.True(AreEqual(expectedSourceIndices[i], sourcePaths[i])); - } + Assert.True(AreEqual(expectedSourceIndices[i], sourcePaths[i])); } + } - ValidateSelection(selectionModel, - new List() - { - Path(1, 0, 1, 0), - Path(1, 0, 1, 1), - Path(1, 0, 1, 2), - Path(1, 0, 1, 3), - Path(1, 0, 1), - Path(1, 1, 0, 0), - Path(1, 1, 0, 1), - Path(1, 1, 0, 2), - Path(1, 1, 0, 3), - Path(1, 1, 0), - Path(1, 1, 1, 0), - }, - new List() - { - Path(), - Path(1), - Path(1, 0), - Path(1, 1), - Path(1, 1, 1), - }, - 2 /* selectedInnerNodes */); - - ClearSelection(selectionModel); - ValidateSelection(selectionModel, new List() { }); - - startPath = Path(0, 1, 0, 2); - SetAnchorIndex(selectionModel, startPath); - endPath = Path(0, 0, 0, 2); - SelectRangeFromAnchor(selectionModel, endPath, true /* select */); - ValidateSelection(selectionModel, - new List() - { - 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, 0, 1), - Path(0, 1, 0, 0), - Path(0, 1, 0, 1), - Path(0, 1, 0, 2), - }, - new List() - { - Path(), - Path(0), - Path(0, 0), - Path(0, 0, 0), - Path(0, 1), - Path(0, 1, 0), - }, - 1 /* selectedInnerNodes */); - - startPath = Path(0, 1, 0, 2); - SetAnchorIndex(selectionModel, startPath); - endPath = Path(0, 0, 0, 2); - SelectRangeFromAnchor(selectionModel, endPath, false /* select */); - ValidateSelection(selectionModel, new List() { }); - }); + ValidateSelection(selectionModel, + new List() + { + Path(1, 0, 1, 0), + Path(1, 0, 1, 1), + Path(1, 0, 1, 2), + Path(1, 0, 1, 3), + Path(1, 0, 1), + Path(1, 1, 0, 0), + Path(1, 1, 0, 1), + Path(1, 1, 0, 2), + Path(1, 1, 0, 3), + Path(1, 1, 0), + Path(1, 1, 1, 0), + }, + new List() + { + Path(), + Path(1), + Path(1, 0), + Path(1, 1), + Path(1, 1, 1), + }, + 2 /* selectedInnerNodes */); + + ClearSelection(selectionModel); + ValidateSelection(selectionModel, new List() { }); + + startPath = Path(0, 1, 0, 2); + SetAnchorIndex(selectionModel, startPath); + endPath = Path(0, 0, 0, 2); + SelectRangeFromAnchor(selectionModel, endPath, true /* select */); + ValidateSelection(selectionModel, + new List() + { + 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, 0, 1), + Path(0, 1, 0, 0), + Path(0, 1, 0, 1), + Path(0, 1, 0, 2), + }, + new List() + { + Path(), + Path(0), + Path(0, 0), + Path(0, 0, 0), + Path(0, 1), + Path(0, 1, 0), + }, + 1 /* selectedInnerNodes */); + + startPath = Path(0, 1, 0, 2); + SetAnchorIndex(selectionModel, startPath); + endPath = Path(0, 0, 0, 2); + SelectRangeFromAnchor(selectionModel, endPath, false /* select */); + ValidateSelection(selectionModel, new List() { }); } [Fact] public void ValidateInserts() { - RunOnUIThread.Execute(() => - { - var data = new ObservableCollection(Enumerable.Range(0, 10)); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; - - selectionModel.Select(3); - selectionModel.Select(4); - selectionModel.Select(5); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(4), - Path(5), - }, - new List() - { - Path() - }); - - Log.Comment("Insert in selected range: Inserting 3 items at index 4"); - data.Insert(4, 41); - data.Insert(4, 42); - data.Insert(4, 43); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(7), - Path(8), - }, - new List() - { - Path() - }); - - Log.Comment("Insert before selected range: Inserting 3 items at index 0"); - data.Insert(0, 100); - data.Insert(0, 101); - data.Insert(0, 102); - ValidateSelection(selectionModel, - new List() - { - Path(6), - Path(10), - Path(11), - }, - new List() - { - Path() - }); - - Log.Comment("Insert after selected range: Inserting 3 items at index 12"); - data.Insert(12, 1000); - data.Insert(12, 1001); - data.Insert(12, 1002); - ValidateSelection(selectionModel, - new List() - { - Path(6), - Path(10), - Path(11), - }, - new List() - { - Path() - }); - }); + var data = new ObservableCollection(Enumerable.Range(0, 10)); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; + + selectionModel.Select(3); + selectionModel.Select(4); + selectionModel.Select(5); + ValidateSelection(selectionModel, + new List() + { + Path(3), + Path(4), + Path(5), + }, + new List() + { + Path() + }); + + _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, + new List() + { + Path(3), + Path(7), + Path(8), + }, + new List() + { + Path() + }); + + _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, + new List() + { + Path(6), + Path(10), + Path(11), + }, + new List() + { + Path() + }); + + _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, + new List() + { + Path(6), + Path(10), + Path(11), + }, + new List() + { + Path() + }); } [Fact] public void ValidateGroupInserts() { - RunOnUIThread.Execute(() => - { - var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; + var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; - selectionModel.Select(1, 1); - ValidateSelection(selectionModel, - new List() - { - Path(1, 1), - }, - new List() - { - Path(), - Path(1), - }); - - Log.Comment("Insert before selected range: Inserting item at group index 0"); - data.Insert(0, 100); - ValidateSelection(selectionModel, - new List() - { - Path(2, 1) - }, - new List() - { - Path(), - Path(2), - }); - - Log.Comment("Insert after selected range: Inserting item at group index 3"); - data.Insert(3, 1000); - ValidateSelection(selectionModel, - new List() - { - Path(2, 1) - }, - new List() - { - Path(), - Path(2), - }); - }); + selectionModel.Select(1, 1); + ValidateSelection(selectionModel, + new List() + { + Path(1, 1), + }, + new List() + { + Path(), + Path(1), + }); + + _output.WriteLine("Insert before selected range: Inserting item at group index 0"); + data.Insert(0, 100); + ValidateSelection(selectionModel, + new List() + { + Path(2, 1) + }, + new List() + { + Path(), + Path(2), + }); + + _output.WriteLine("Insert after selected range: Inserting item at group index 3"); + data.Insert(3, 1000); + ValidateSelection(selectionModel, + new List() + { + Path(2, 1) + }, + new List() + { + Path(), + Path(2), + }); } [Fact] public void ValidateRemoves() { - RunOnUIThread.Execute(() => - { - var data = new ObservableCollection(Enumerable.Range(0, 10)); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; - - selectionModel.Select(6); - selectionModel.Select(7); - selectionModel.Select(8); - ValidateSelection(selectionModel, - new List() - { - Path(6), - Path(7), - Path(8) - }, - new List() - { - Path() - }); + var data = new ObservableCollection(Enumerable.Range(0, 10)); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; + + selectionModel.Select(6); + selectionModel.Select(7); + selectionModel.Select(8); + ValidateSelection(selectionModel, + new List() + { + Path(6), + Path(7), + Path(8) + }, + new List() + { + Path() + }); - Log.Comment("Remove before selected range: Removing item at index 0"); - data.RemoveAt(0); - ValidateSelection(selectionModel, - new List() - { - Path(5), - Path(6), - Path(7) - }, - new List() - { - Path() - }); - - Log.Comment("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, - new List() - { - Path(3), - Path(4) - }, - new List() - { - Path() - }); + _output.WriteLine("Remove before selected range: Removing item at index 0"); + data.RemoveAt(0); + ValidateSelection(selectionModel, + new List() + { + Path(5), + Path(6), + Path(7) + }, + new List() + { + Path() + }); + + _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, + new List() + { + Path(3), + Path(4) + }, + new List() + { + Path() + }); - Log.Comment("Remove after selected range: Removing item at index 5"); - data.RemoveAt(5); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(4) - }, - new List() - { - Path() - }); - }); + _output.WriteLine("Remove after selected range: Removing item at index 5"); + data.RemoveAt(5); + ValidateSelection(selectionModel, + new List() + { + Path(3), + Path(4) + }, + new List() + { + Path() + }); } [Fact] public void ValidateGroupRemoves() { - RunOnUIThread.Execute(() => - { - 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, - new List() - { - Path(1, 1), - Path(1, 2) - }, - new List() - { - Path(), - Path(1), - }); - - Log.Comment("Remove before selected range: Removing item at group index 0"); - data.RemoveAt(0); - ValidateSelection(selectionModel, - new List() - { - Path(0, 1), - Path(0, 2) - }, - new List() - { - Path(), - Path(0), - }); - - Log.Comment("Remove after selected range: Removing item at group index 1"); - data.RemoveAt(1); - ValidateSelection(selectionModel, - new List() - { - Path(0, 1), - Path(0, 2) - }, - new List() - { - Path(), - Path(0), - }); + 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, + new List() + { + Path(1, 1), + Path(1, 2) + }, + new List() + { + Path(), + Path(1), + }); + + _output.WriteLine("Remove before selected range: Removing item at group index 0"); + data.RemoveAt(0); + ValidateSelection(selectionModel, + new List() + { + Path(0, 1), + Path(0, 2) + }, + new List() + { + Path(), + Path(0), + }); + + _output.WriteLine("Remove after selected range: Removing item at group index 1"); + data.RemoveAt(1); + ValidateSelection(selectionModel, + new List() + { + Path(0, 1), + Path(0, 2) + }, + new List() + { + Path(), + Path(0), + }); - Log.Comment("Remove group containing selected items"); - data.RemoveAt(0); - ValidateSelection(selectionModel, new List()); - }); + _output.WriteLine("Remove group containing selected items"); + data.RemoveAt(0); + ValidateSelection(selectionModel, new List()); } [Fact] public void CanReplaceItem() { - RunOnUIThread.Execute(() => - { - var data = new ObservableCollection(Enumerable.Range(0, 10)); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; - - selectionModel.Select(3); - selectionModel.Select(4); - selectionModel.Select(5); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(4), - Path(5), - }, - new List() - { - Path() - }); + var data = new ObservableCollection(Enumerable.Range(0, 10)); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; + + selectionModel.Select(3); + selectionModel.Select(4); + selectionModel.Select(5); + ValidateSelection(selectionModel, + new List() + { + Path(3), + Path(4), + Path(5), + }, + new List() + { + Path() + }); - data[3] = 300; - data[4] = 400; - ValidateSelection(selectionModel, - new List() - { - Path(5), - }, - new List() - { - Path() - }); - }); + data[3] = 300; + data[4] = 400; + ValidateSelection(selectionModel, + new List() + { + Path(5), + }, + new List() + { + Path() + }); } [Fact] public void ValidateGroupReplaceLosesSelection() { - RunOnUIThread.Execute(() => - { - var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; + var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; - selectionModel.Select(1, 1); - ValidateSelection(selectionModel, - new List() - { - Path(1, 1) - }, - new List() - { - Path(), - Path(1) - }); + selectionModel.Select(1, 1); + ValidateSelection(selectionModel, + new List() + { + Path(1, 1) + }, + new List() + { + Path(), + Path(1) + }); - data[1] = new ObservableCollection(Enumerable.Range(0, 5)); - ValidateSelection(selectionModel, new List()); - }); + data[1] = new ObservableCollection(Enumerable.Range(0, 5)); + ValidateSelection(selectionModel, new List()); } [Fact] public void ValidateClear() { - RunOnUIThread.Execute(() => - { - var data = new ObservableCollection(Enumerable.Range(0, 10)); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; - - selectionModel.Select(3); - selectionModel.Select(4); - selectionModel.Select(5); - ValidateSelection(selectionModel, - new List() - { - Path(3), - Path(4), - Path(5), - }, - new List() - { - Path() - }); + var data = new ObservableCollection(Enumerable.Range(0, 10)); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; + + selectionModel.Select(3); + selectionModel.Select(4); + selectionModel.Select(5); + ValidateSelection(selectionModel, + new List() + { + Path(3), + Path(4), + Path(5), + }, + new List() + { + Path() + }); - data.Clear(); - ValidateSelection(selectionModel, new List()); - }); + data.Clear(); + ValidateSelection(selectionModel, new List()); } [Fact] public void ValidateGroupClear() { - RunOnUIThread.Execute(() => - { - var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); - var selectionModel = new SelectionModel(); - selectionModel.Source = data; + var data = CreateNestedData(1 /* levels */ , 3 /* groupsAtLevel */, 3 /* countAtLeaf */); + var selectionModel = new SelectionModel(); + selectionModel.Source = data; - selectionModel.Select(1, 1); - ValidateSelection(selectionModel, - new List() - { - Path(1, 1) - }, - new List() - { - Path(), - Path(1) - }); + selectionModel.Select(1, 1); + ValidateSelection(selectionModel, + new List() + { + Path(1, 1) + }, + new List() + { + Path(), + Path(1) + }); - (data[1] as IList).Clear(); - ValidateSelection(selectionModel, new List()); - }); + (data[1] as IList).Clear(); + ValidateSelection(selectionModel, new List()); } // In some cases the leaf node might get a collection change that affects an ancestors selection @@ -767,167 +716,155 @@ namespace Avalonia.Controls.UnitTests [Fact] public void ValidateEventWhenInnerNodeChangesSelectionState() { - RunOnUIThread.Execute(() => - { - 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, - new List() - { - Path(1, 0), - Path(1, 1), - Path(1, 2), - Path(1) - }, - new List() - { - Path(), - }, - 1 /* selectedInnerNodes */); - - Log.Comment("Inserting 1.0"); - selectionChangedRaised = false; - (data[1] as AvaloniaList).Insert(0, 100); - Assert.True(selectionChangedRaised, "SelectionChanged event was not raised"); - ValidateSelection(selectionModel, - new List() - { - Path(1, 1), - Path(1, 2), - Path(1, 3), - }, - new List() - { - Path(), - Path(1), - }); - - Log.Comment("Removing 1.0"); - selectionChangedRaised = false; - (data[1] as AvaloniaList).RemoveAt(0); - Assert.True(selectionChangedRaised, "SelectionChanged event was not raised"); - ValidateSelection(selectionModel, - new List() - { - Path(1, 0), - Path(1, 1), - Path(1, 2), - Path(1) - }, - new List() - { - Path(), - }, - 1 /* selectedInnerNodes */); - }); + 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, + new List() + { + Path(1, 0), + Path(1, 1), + Path(1, 2), + Path(1) + }, + new List() + { + Path(), + }, + 1 /* selectedInnerNodes */); + + _output.WriteLine("Inserting 1.0"); + selectionChangedRaised = false; + (data[1] as AvaloniaList).Insert(0, 100); + Assert.True(selectionChangedRaised, "SelectionChanged event was not raised"); + ValidateSelection(selectionModel, + new List() + { + Path(1, 1), + Path(1, 2), + Path(1, 3), + }, + new List() + { + Path(), + Path(1), + }); + + _output.WriteLine("Removing 1.0"); + selectionChangedRaised = false; + (data[1] as AvaloniaList).RemoveAt(0); + Assert.True(selectionChangedRaised, "SelectionChanged event was not raised"); + ValidateSelection(selectionModel, + new List() + { + Path(1, 0), + Path(1, 1), + Path(1, 2), + Path(1) + }, + new List() + { + Path(), + }, + 1 /* selectedInnerNodes */); } [Fact] public void ValidatePropertyChangedEventIsRaised() { - RunOnUIThread.Execute(() => + 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) => { - var selectionModel = new SelectionModel(); - Log.Comment("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(); - } - }; + 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); + Select(selectionModel, 3, true); - Assert.True(selectedIndexChanged); - Assert.True(selectedIndicesChanged); - Assert.True(SelectedItemChanged); - Assert.True(SelectedItemsChanged); - Assert.True(AnchorIndexChanged); - }); + Assert.True(selectedIndexChanged); + Assert.True(selectedIndicesChanged); + Assert.True(SelectedItemChanged); + Assert.True(SelectedItemsChanged); + Assert.True(AnchorIndexChanged); } [Fact] public void CanExtendSelectionModelINPC() { - RunOnUIThread.Execute(() => + var selectionModel = new CustomSelectionModel(); + bool intPropertyChanged = false; + selectionModel.PropertyChanged += (sender, args) => { - var selectionModel = new CustomSelectionModel(); - bool intPropertyChanged = false; - selectionModel.PropertyChanged += (sender, args) => + if (args.PropertyName == "IntProperty") { - if (args.PropertyName == "IntProperty") - { - intPropertyChanged = true; - } - }; + intPropertyChanged = true; + } + }; - selectionModel.IntProperty = 5; - Assert.True(intPropertyChanged); - }); + selectionModel.IntProperty = 5; + Assert.True(intPropertyChanged); } [Fact] public void SelectRangeRegressionTest() { - RunOnUIThread.Execute(() => + var selectionModel = new SelectionModel() { - var selectionModel = new SelectionModel() - { - Source = CreateNestedData(1, 2, 3) - }; + 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, - new List() - { - Path(0, 0), - Path(0, 1), - Path(0, 2), - Path(0), - Path(1, 0), - Path(1, 1) - }, - new List() - { - Path(), - Path(1) - }, - 1 /* selectedInnerNodes */); - }); + // 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, + new List() + { + Path(0, 0), + Path(0, 1), + Path(0, 2), + Path(0), + Path(1, 0), + Path(1, 1) + }, + new List() + { + Path(), + Path(1) + }, + 1 /* selectedInnerNodes */); } [Fact] @@ -1011,7 +948,7 @@ namespace Avalonia.Controls.UnitTests private void Select(SelectionModel manager, int index, bool select) { - Log.Comment((select ? "Selecting " : "DeSelecting ") + index); + _output.WriteLine((select ? "Selecting " : "DeSelecting ") + index); if (select) { manager.Select(index); @@ -1024,7 +961,7 @@ namespace Avalonia.Controls.UnitTests private void Select(SelectionModel manager, int groupIndex, int itemIndex, bool select) { - Log.Comment((select ? "Selecting " : "DeSelecting ") + groupIndex + "." + itemIndex); + _output.WriteLine((select ? "Selecting " : "DeSelecting ") + groupIndex + "." + itemIndex); if (select) { manager.Select(groupIndex, itemIndex); @@ -1037,7 +974,7 @@ namespace Avalonia.Controls.UnitTests private void Select(SelectionModel manager, IndexPath index, bool select) { - Log.Comment((select ? "Selecting " : "DeSelecting ") + index); + _output.WriteLine((select ? "Selecting " : "DeSelecting ") + index); if (select) { manager.SelectAt(index); @@ -1050,7 +987,7 @@ namespace Avalonia.Controls.UnitTests private void SelectRangeFromAnchor(SelectionModel manager, int index, bool select) { - Log.Comment("SelectRangeFromAnchor " + index + " select: " + select.ToString()); + _output.WriteLine("SelectRangeFromAnchor " + index + " select: " + select.ToString()); if (select) { manager.SelectRangeFromAnchor(index); @@ -1063,7 +1000,7 @@ namespace Avalonia.Controls.UnitTests private void SelectRangeFromAnchor(SelectionModel manager, int groupIndex, int itemIndex, bool select) { - Log.Comment("SelectRangeFromAnchor " + groupIndex + "." + itemIndex + " select:" + select.ToString()); + _output.WriteLine("SelectRangeFromAnchor " + groupIndex + "." + itemIndex + " select:" + select.ToString()); if (select) { manager.SelectRangeFromAnchor(groupIndex, itemIndex); @@ -1076,7 +1013,7 @@ namespace Avalonia.Controls.UnitTests private void SelectRangeFromAnchor(SelectionModel manager, IndexPath index, bool select) { - Log.Comment("SelectRangeFromAnchor " + index + " select: " + select.ToString()); + _output.WriteLine("SelectRangeFromAnchor " + index + " select: " + select.ToString()); if (select) { manager.SelectRangeFromAnchorTo(index); @@ -1089,25 +1026,25 @@ namespace Avalonia.Controls.UnitTests private void ClearSelection(SelectionModel manager) { - Log.Comment("ClearSelection"); + _output.WriteLine("ClearSelection"); manager.ClearSelection(); } private void SetAnchorIndex(SelectionModel manager, int index) { - Log.Comment("SetAnchorIndex " + index); + _output.WriteLine("SetAnchorIndex " + index); manager.SetAnchorIndex(index); } private void SetAnchorIndex(SelectionModel manager, int groupIndex, int itemIndex) { - Log.Comment("SetAnchor " + groupIndex + "." + itemIndex); + _output.WriteLine("SetAnchor " + groupIndex + "." + itemIndex); manager.SetAnchorIndex(groupIndex, itemIndex); } private void SetAnchorIndex(SelectionModel manager, IndexPath index) { - Log.Comment("SetAnchor " + index); + _output.WriteLine("SetAnchor " + index); manager.AnchorIndex = index; } @@ -1117,18 +1054,18 @@ namespace Avalonia.Controls.UnitTests List expectedPartialSelected = null, int selectedInnerNodes = 0) { - Log.Comment("Validating Selection..."); + _output.WriteLine("Validating Selection..."); - Log.Comment("Selection contains indices:"); + _output.WriteLine("Selection contains indices:"); foreach (var index in selectionModel.SelectedIndices) { - Log.Comment(" " + index.ToString()); + _output.WriteLine(" " + index.ToString()); } - Log.Comment("Selection contains items:"); + _output.WriteLine("Selection contains items:"); foreach (var item in selectionModel.SelectedItems) { - Log.Comment(" " + item.ToString()); + _output.WriteLine(" " + item.ToString()); } if (selectionModel.Source != null) @@ -1149,7 +1086,7 @@ namespace Avalonia.Controls.UnitTests { if (isSelected == null) { - Log.Comment("*************" + index + " is null"); + _output.WriteLine("*************" + index + " is null"); Assert.True(false, "Expected false but got null");; } else @@ -1168,7 +1105,7 @@ namespace Avalonia.Controls.UnitTests } if (expectedSelected.Count > 0) { - Log.Comment("SelectedIndex is " + selectionModel.SelectedIndex); + _output.WriteLine("SelectedIndex is " + selectionModel.SelectedIndex); Assert.Equal(0, selectionModel.SelectedIndex.CompareTo(expectedSelected[0])); if (selectionModel.Source != null) { @@ -1181,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(expectedSelected.Count - selectedInnerNodes, indicesCount); } - Log.Comment("Validating Selection... done"); + _output.WriteLine("Validating Selection... done"); } private object GetData(SelectionModel selectionModel, IndexPath indexPath) @@ -1225,12 +1162,12 @@ namespace Avalonia.Controls.UnitTests } }); - Log.Comment("All Paths in source.."); + _output.WriteLine("All Paths in source.."); foreach (var path in paths) { - Log.Comment(path.ToString()); + _output.WriteLine(path.ToString()); } - Log.Comment("done."); + _output.WriteLine("done."); return paths; } @@ -1328,18 +1265,6 @@ namespace Avalonia.Controls.UnitTests public IndexPath Path { get; set; } } - - private static class RunOnUIThread - { - public static void Execute(Action a) => a(); - } - - private class LogWrapper - { - private readonly ITestOutputHelper _output; - public LogWrapper(ITestOutputHelper output) => _output = output; - public void Comment(string s) => _output.WriteLine(s); - } } class CustomSelectionModel : SelectionModel