From cbd9badc928084eda2a9baf6aa2e39911980a019 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 28 Jan 2020 14:08:08 +0100 Subject: [PATCH] Merge pull request #3464 from Rfvgyhn/datagrid-multisort Fix datagrid ascending multi-column sort --- Avalonia.sln | 27 ++++ nukebuild/Build.cs | 1 + .../Collections/DataGridSortDescription.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- ...valonia.Controls.DataGrid.UnitTests.csproj | 22 +++ .../DataGridSortDescriptionTests.cs | 132 ++++++++++++++++++ 6 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj create mode 100644 tests/Avalonia.Controls.DataGrid.UnitTests/Collections/DataGridSortDescriptionTests.cs diff --git a/Avalonia.sln b/Avalonia.sln index 568a16ce0e..7a2f785bb9 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -203,6 +203,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -1894,6 +1896,30 @@ Global {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.Build.0 = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.Build.0 = Release|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.ActiveCfg = Release|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1950,6 +1976,7 @@ Global {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index dd2f27116d..7b3b8465ce 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -191,6 +191,7 @@ partial class Build : NukeBuild RunCoreTest("./tests/Avalonia.Animation.UnitTests"); RunCoreTest("./tests/Avalonia.Base.UnitTests"); RunCoreTest("./tests/Avalonia.Controls.UnitTests"); + RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests"); RunCoreTest("./tests/Avalonia.Input.UnitTests"); RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); RunCoreTest("./tests/Avalonia.Layout.UnitTests"); diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs index 86113da87e..bea6f01243 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs @@ -238,7 +238,7 @@ namespace Avalonia.Collections } else { - return seq.ThenByDescending(o => GetValue(o), InternalComparer); + return seq.ThenBy(o => GetValue(o), InternalComparer); } } diff --git a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs index f15442addf..489bfc31d0 100644 --- a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj new file mode 100644 index 0000000000..7fec6f2770 --- /dev/null +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj @@ -0,0 +1,22 @@ + + + netcoreapp2.0;net47 + latest + Library + true + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Collections/DataGridSortDescriptionTests.cs b/tests/Avalonia.Controls.DataGrid.UnitTests/Collections/DataGridSortDescriptionTests.cs new file mode 100644 index 0000000000..a1a734f650 --- /dev/null +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Collections/DataGridSortDescriptionTests.cs @@ -0,0 +1,132 @@ +using System; +using System.Linq; +using Avalonia.Collections; +using Xunit; + +namespace Avalonia.Controls.DataGrid.UnitTests.Collections +{ + + public class DataGridSortDescriptionTests + { + [Fact] + public void OrderBy_Orders_Correctly_When_Ascending() + { + var items = new[] + { + new Item("b", "b"), + new Item("a", "a"), + new Item("c", "c"), + }; + var expectedResult = items.OrderBy(i => i.Prop1).ToList(); + var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop1), @descending: false); + + sortDescription.Initialize(typeof(Item)); + var result = sortDescription.OrderBy(items).ToList(); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void OrderBy_Orders_Correctly_When_Descending() + { + var items = new[] + { + new Item("b", "b"), + new Item("a", "a"), + new Item("c", "c"), + }; + var expectedResult = items.OrderByDescending(i => i.Prop1).ToList(); + var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop1), @descending: true); + + sortDescription.Initialize(typeof(Item)); + var result = sortDescription.OrderBy(items).ToList(); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ThenBy_Orders_Correctly_When_Ascending() + { + // Casting nonsense below because IOrderedEnumerable isn't covariant in full framework and we need an + // object of type IOrderedEnumerable for DataGridSortDescription.ThenBy + var items = new[] + { + (object)new Item("a", "b"), + new Item("a", "a"), + new Item("a", "c"), + }.OrderBy(i => ((Item)i).Prop1); + var expectedResult = new[] + { + new Item("a", "a"), + new Item("a", "b"), + new Item("a", "c"), + }; + var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop2), @descending: false); + + sortDescription.Initialize(typeof(Item)); + var result = sortDescription.ThenBy(items).ToList(); + + Assert.Equal(expectedResult, result); + } + + [Fact] + public void ThenBy_Orders_Correctly_When_Descending() + { + // Casting nonsense below because IOrderedEnumerable isn't covariant in full framework and we need an + // object of type IOrderedEnumerable for DataGridSortDescription.ThenBy + var items = new[] + { + (object)new Item("a", "b"), + new Item("a", "a"), + new Item("a", "c"), + }.OrderBy(i => ((Item)i).Prop1); + var expectedResult = new[] + { + new Item("a", "c"), + new Item("a", "b"), + new Item("a", "a"), + }; + var sortDescription = DataGridSortDescription.FromPath(nameof(Item.Prop2), @descending: true); + + sortDescription.Initialize(typeof(Item)); + var result = sortDescription.ThenBy(items).ToList(); + + Assert.Equal(expectedResult, result); + } + + private class Item : IEquatable + { + public Item(string prop1, string prop2) + { + Prop1 = prop1; + Prop2 = prop2; + } + + public string Prop1 { get; } + public string Prop2 { get; } + + public bool Equals(Item other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Prop1 == other.Prop1 && Prop2 == other.Prop2; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Item) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((Prop1 != null ? Prop1.GetHashCode() : 0) * 397) ^ (Prop2 != null ? Prop2.GetHashCode() : 0); + } + } + } + } +}