diff --git a/Maui.DataGrid.Sample.sln b/Maui.DataGrid.Sample.sln index b44a4c6..0fd97d9 100644 --- a/Maui.DataGrid.Sample.sln +++ b/Maui.DataGrid.Sample.sln @@ -20,6 +20,7 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Test|Any CPU = Test|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -28,10 +29,15 @@ Global {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Release|Any CPU.ActiveCfg = Release|Any CPU {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Release|Any CPU.Build.0 = Release|Any CPU {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Release|Any CPU.Deploy.0 = Release|Any CPU + {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Test|Any CPU.ActiveCfg = Test|Any CPU + {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Test|Any CPU.Build.0 = Test|Any CPU + {1A19A0CA-B02B-4DDB-B8F6-39FAAC6263E0}.Test|Any CPU.Deploy.0 = Test|Any CPU {3CAFEEB9-BB8E-40FE-B85B-F0ACD6A66429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3CAFEEB9-BB8E-40FE-B85B-F0ACD6A66429}.Debug|Any CPU.Build.0 = Debug|Any CPU {3CAFEEB9-BB8E-40FE-B85B-F0ACD6A66429}.Release|Any CPU.ActiveCfg = Release|Any CPU {3CAFEEB9-BB8E-40FE-B85B-F0ACD6A66429}.Release|Any CPU.Build.0 = Release|Any CPU + {3CAFEEB9-BB8E-40FE-B85B-F0ACD6A66429}.Test|Any CPU.ActiveCfg = Test|Any CPU + {3CAFEEB9-BB8E-40FE-B85B-F0ACD6A66429}.Test|Any CPU.Build.0 = Test|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Maui.DataGrid.Sample/Maui.DataGrid.Sample.csproj b/Maui.DataGrid.Sample/Maui.DataGrid.Sample.csproj index 0aa7470..1f283db 100644 --- a/Maui.DataGrid.Sample/Maui.DataGrid.Sample.csproj +++ b/Maui.DataGrid.Sample/Maui.DataGrid.Sample.csproj @@ -1,4 +1,4 @@ - + net8.0-android;net8.0-ios;net8.0-maccatalyst @@ -32,6 +32,7 @@ Maui DataGrid Example Ebubekir Akgul + Debug;Release;Test @@ -87,6 +88,7 @@ + diff --git a/Maui.DataGrid.Sample/MauiProgram.cs b/Maui.DataGrid.Sample/MauiProgram.cs index d51c24f..e3d7d02 100644 --- a/Maui.DataGrid.Sample/MauiProgram.cs +++ b/Maui.DataGrid.Sample/MauiProgram.cs @@ -1,12 +1,28 @@ namespace Maui.DataGrid.Sample; +#if TEST +using Xunit.Runners.Maui; +#else using CommunityToolkit.Maui; +#endif public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); +#if TEST + builder.ConfigureTests(new TestOptions + { + Assemblies = + { + typeof(MauiProgram).Assembly + } + }) +.UseVisualRunner(); +#else + + _ = builder .UseMauiApp() #if DEBUG @@ -19,11 +35,13 @@ public static MauiApp CreateMauiApp() options.SetShouldSuppressExceptionsInConverters(true); }) #endif - .ConfigureFonts(fonts => +.ConfigureFonts(fonts => { _ = fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); _ = fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); +#endif + return builder.Build(); } diff --git a/Maui.DataGrid.Sample/Tests/ColumnsTest.cs b/Maui.DataGrid.Sample/Tests/ColumnsTest.cs new file mode 100644 index 0000000..892c854 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/ColumnsTest.cs @@ -0,0 +1,85 @@ +namespace Maui.DataGrid.Sample.Tests; + +using System.Collections.ObjectModel; +using Maui.DataGrid.Sample.Tests.TestUtils; +using Xunit; + +public class ColumnsTest +{ + private readonly List _columns = + [ + new() { Title = "Name", PropertyName = "Name" }, + new() { Title = "Won", PropertyName = "Won" }, + new() { Title = "Lost", PropertyName = "Lost" } + ]; + + [Fact] + public async void TestColumnsBindingFromViewModel() + { + var columns = new ObservableCollection(_columns); + + var viewModel = new SingleVM>(); + var dataGrid = new DataGrid(); + + dataGrid.SetBinding(DataGrid.ColumnsProperty, new Binding("Item", source: viewModel)); + Assert.Null(dataGrid.Columns); + + var propertyChangedEventTriggered = false; + dataGrid.PropertyChanged += (s, e) => + { + if (e.PropertyName == DataGrid.ColumnsProperty.PropertyName) + { + propertyChangedEventTriggered = true; + } + }; + + viewModel.Item = columns; + Assert.Equal(columns, await dataGrid.GetValueSafe(DataGrid.ColumnsProperty)); + Assert.True(propertyChangedEventTriggered); + + columns.RemoveAt(2); + + var newColumns = await dataGrid.GetValueSafe(DataGrid.ColumnsProperty) as ObservableCollection; + Assert.NotNull(newColumns); + Assert.Equal(2, dataGrid.Columns.Count); + Assert.Equal("Name", dataGrid.Columns[0].Title); + Assert.Equal("Won", dataGrid.Columns[1].Title); + } + + + [Fact] + public async void SortOrderBindingOnlyWorksWhenLoaded() + { + var dataGrid = new DataGrid + { + Columns = new ObservableCollection(_columns), + }; + + var viewModel = new SingleVM(); + + dataGrid.SetBinding(DataGrid.SortedColumnIndexProperty, new Binding("Item", source: viewModel)); + Assert.Null(dataGrid.SortedColumnIndex); + + var propertyChangedEventTriggered = false; + dataGrid.PropertyChanged += (s, e) => + { + if (e.PropertyName == DataGrid.SortedColumnIndexProperty.PropertyName) + { + propertyChangedEventTriggered = true; + } + }; + + viewModel.Item = -1; + if (!dataGrid.IsLoaded) + { + Assert.Null(await dataGrid.GetValueSafe(DataGrid.SortedColumnIndexProperty)); + Assert.False(propertyChangedEventTriggered); + return; + } + else + { + Assert.Equal(-1, await dataGrid.GetValueSafe(DataGrid.SortedColumnIndexProperty)); + Assert.True(propertyChangedEventTriggered); + } + } +} diff --git a/Maui.DataGrid.Sample/Tests/ItemsSourceTest.cs b/Maui.DataGrid.Sample/Tests/ItemsSourceTest.cs new file mode 100644 index 0000000..dbaddb2 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/ItemsSourceTest.cs @@ -0,0 +1,80 @@ +namespace Maui.DataGrid.Sample.Tests; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Maui.DataGrid.Sample.Models; +using Maui.DataGrid.Sample.Tests.TestUtils; +using Xunit; + +public class ItemsSourceTest +{ + private readonly List _teams = Utils.DummyDataProvider.GetTeams(); + private readonly Team _dummyTeam = new() + { + Name = "Not Exists", + Conf = "", + Div = "", + Home = "", + Last10 = "", + Logo = "", + Road = "", + Streak = new Streak { NumStreak = 3, Result = Result.Lost } + }; + + [Fact] + public void BindsItemSource() + { + var dataGrid = new DataGrid(); + dataGrid.CheckPropertyBindingWorks(DataGrid.ItemsSourceProperty, _teams, null); + } + + [Fact] + public void BindsSelectedItem() + { + var datagrid = new DataGrid { ItemsSource = _teams }; + datagrid.CheckPropertyBindingWorks(DataGrid.SelectedItemProperty, _teams.ElementAt(2), _teams.ElementAt(3)); + } + + + [Fact] + public async void SelectNonExistingItemNotPossible() + { + var viewModel = new SingleVM(); + var datagrid = new DataGrid { ItemsSource = _teams }; + + datagrid.SetBinding(DataGrid.SelectedItemProperty, new Binding("Item", source: viewModel)); + + viewModel.Item = _teams.First(); + Assert.Equal(_teams.First(), await datagrid.GetValueSafe(DataGrid.SelectedItemProperty)); + + viewModel.Item = _dummyTeam; + Assert.Null(await datagrid.GetValueSafe(DataGrid.SelectedItemProperty)); + } + + [Fact] + public async void RemovingItemInObservableCollectionUpdatesItemsSource() + { + var viewModel = new SingleVM> { Item = new ObservableCollection(_teams) }; + var datagrid = new DataGrid(); + datagrid.SetBinding(DataGrid.ItemsSourceProperty, new Binding("Item", source: viewModel)); + + viewModel.Item.RemoveAt(2); + var itemsSource = await datagrid.GetValueSafe(DataGrid.ItemsSourceProperty) as ObservableCollection; + Assert.NotNull(itemsSource); + Assert.Equal(_teams.Count - 1, itemsSource!.Count); + Assert.DoesNotContain(_teams.ElementAt(2), itemsSource); + } + + [Fact] + public async void AddingItemInObservableCollectionUpdatesItemsSource() + { + var viewModel = new SingleVM> { Item = new ObservableCollection(_teams) }; + var datagrid = new DataGrid(); + datagrid.SetBinding(DataGrid.ItemsSourceProperty, new Binding("Item", source: viewModel)); + + viewModel.Item.Add(_dummyTeam); + var itemsSource = await datagrid.GetValueSafe(DataGrid.ItemsSourceProperty) as ObservableCollection; + Assert.NotNull(itemsSource); + Assert.Equal(_teams.Count + 1, itemsSource!.Count); + Assert.Contains(_dummyTeam, itemsSource); + } +} diff --git a/Maui.DataGrid.Sample/Tests/PaginationTest.cs b/Maui.DataGrid.Sample/Tests/PaginationTest.cs new file mode 100644 index 0000000..8c0bf85 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/PaginationTest.cs @@ -0,0 +1,103 @@ +namespace Maui.DataGrid.Sample.Tests; +using System.Collections.Generic; +using Maui.DataGrid.Sample.Models; +using Maui.DataGrid.Sample.Tests.TestUtils; +using Xunit; + +public class PaginationTest +{ + private readonly List _teams = Utils.DummyDataProvider.GetTeams(); + + [Fact] + public void PageCountDoesNotChangesWithBinding() + { + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 10 }; + + Assert.Equal(2, dataGrid.PageCount); + + var countViewModel = new SingleVM(); + dataGrid.SetBinding(DataGrid.PageCountProperty, new Binding("Item", source: countViewModel)); + + countViewModel.Item = 1; + Assert.Equal(2, dataGrid.PageCount); + + } + + [Fact] + public void PageNumberDoesNotExceedsLimit() + { + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 10 }; + + Assert.Equal(1, dataGrid.PageNumber); + + + dataGrid.PageNumber = 2; + Assert.Equal(2, dataGrid.PageNumber); + + dataGrid.PageNumber = 3; + Assert.Equal(2, dataGrid.PageNumber); + + } + + [Fact] + public void PageSizeAllowsMorePageNumber() + { + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 10 }; + + Assert.Equal(1, dataGrid.PageNumber); + + dataGrid.PageSize = 5; + + dataGrid.PageNumber = 3; + Assert.Equal(3, dataGrid.PageNumber); + + dataGrid.PageNumber = 30; + Assert.Equal(3, dataGrid.PageNumber); + } + + [Fact] + public void PageNumberCannotBeNegative() + { + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 10 }; + + Assert.Equal(1, dataGrid.PageNumber); + + dataGrid.PageNumber = -1; + Assert.Equal(1, dataGrid.PageNumber); + } + + [Fact] + public void PageSizeCannotBeNegative() + { + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 10 }; + + Assert.Equal(10, dataGrid.PageSize); + dataGrid.PageSize = -1; + Assert.Equal(10, dataGrid.PageSize); + } + + [Fact] + public void PageNumberResetsWhenPageSizeChanges() + { +#pragma warning disable IDE0017 // Simplify object initialization + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 6 }; + dataGrid.PageNumber = 3; +#pragma warning restore IDE0017 // Simplify object initialization + Assert.Equal(3, dataGrid.PageNumber); + dataGrid.PageSize = 5; + Assert.Equal(1, dataGrid.PageNumber); + + } + + [Fact] + public void PageSizeListUpdatedWithUnknownNumber() + { + var dataGrid = new DataGrid { ItemsSource = _teams, PageSize = 6 }; + + Assert.DoesNotContain(7, dataGrid.PageSizeList); + dataGrid.PageSize = 7; + Assert.Contains(7, dataGrid.PageSizeList); + + } +} + diff --git a/Maui.DataGrid.Sample/Tests/PaletteCollectionTest.cs b/Maui.DataGrid.Sample/Tests/PaletteCollectionTest.cs new file mode 100644 index 0000000..3dd7ed1 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/PaletteCollectionTest.cs @@ -0,0 +1,45 @@ +namespace Maui.DataGrid.Sample.Tests; +using Xunit; + +public class PaletteCollectionTest +{ + [Fact] + public void EmptyPaletteCollection() + { + var palette = new PaletteCollection(); + + Assert.Empty(palette); + Assert.Equal(palette.GetColor(0, "item"), Colors.White); + + } + + [Fact] + public void PaletteCollectionWithSingleColor() + { + var palette = new PaletteCollection + { + Colors.Red + }; + + _ = Assert.Single(palette); + Assert.Equal(palette.GetColor(0, "item"), Colors.Red); + Assert.Equal(palette.GetColor(1, "item"), Colors.Red); + Assert.Equal(palette.GetColor(2, "item"), Colors.Red); + } + + [Fact] + public void PaletteCollectionWithMultipleColors() + { + var palette = new PaletteCollection + { + Colors.Red, + Colors.Green + }; + + Assert.Equal(2, palette.Count); + Assert.Equal(palette.GetColor(0, "item"), Colors.Red); + Assert.Equal(palette.GetColor(1, "item2"), Colors.Green); + Assert.Equal(palette.GetColor(2, "item3"), Colors.Red); + Assert.Equal(palette.GetColor(3, "item4"), Colors.Green); + } +} diff --git a/Maui.DataGrid.Sample/Tests/PropertyTest.cs b/Maui.DataGrid.Sample/Tests/PropertyTest.cs new file mode 100644 index 0000000..39d3046 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/PropertyTest.cs @@ -0,0 +1,54 @@ +namespace Maui.DataGrid.Sample.Tests; + +using Maui.DataGrid.Sample.Tests.TestUtils; +using Microsoft.Maui.Controls; +using Xunit; + +public class PropertyTest +{ + private static readonly PaletteCollection Palette1 = [Colors.Orange, Colors.Red]; + private static readonly PaletteCollection Palette2 = [Colors.Lime, Colors.Green]; + + [Fact] + public void TestPropertiesSetsProperly() + { + TestProperty(DataGrid.ActiveRowColorProperty, Colors.Orange, Colors.Red); + TestProperty(DataGrid.BorderColorProperty, Colors.Orange, Colors.Red); + TestProperty(DataGrid.BorderThicknessProperty, new Thickness(1, 2, 3, 4), new Thickness(4, 5, 6, 7)); + TestProperty(DataGrid.FontFamilyProperty, "OpenSansSemibold", "OpenSansRegular"); + TestProperty(DataGrid.FontSizeProperty, 10.0, 11.0); + TestProperty(DataGrid.FooterBackgroundProperty, Colors.Orange, Colors.Red); + TestProperty(DataGrid.FooterHeightProperty, 42, 44); + TestProperty(DataGrid.HeaderBackgroundProperty, Colors.Orange, Colors.Red); + TestProperty(DataGrid.HeaderBordersVisibleProperty, true, false); + TestProperty(DataGrid.HeaderHeightProperty, 42, 44); + TestProperty(DataGrid.IsRefreshingProperty, true, false); + TestProperty(DataGrid.IsSortableProperty, true, false); + TestProperty(DataGrid.ItemSizingStrategyProperty, ItemSizingStrategy.MeasureAllItems, ItemSizingStrategy.MeasureFirstItem); + TestProperty(DataGrid.ItemsSourceProperty, new[] { "a", "b", "c" }, new[] { "d", "e" }); + TestProperty(DataGrid.NoDataViewProperty, new ContentView { Background = Colors.Aqua }, new ContentView { Background = Colors.Lime }); + TestProperty(DataGrid.PageSizeVisibleProperty, true, false); + TestProperty(DataGrid.PaginationEnabledProperty, true, false); + TestProperty(DataGrid.PullToRefreshCommandParameterProperty, "param1", "param2"); + TestProperty(DataGrid.PullToRefreshCommandProperty, new Command(Command1), new Command(Command2)); + TestProperty(DataGrid.RefreshColorProperty, Colors.Orange, Colors.Red); + TestProperty(DataGrid.RefreshingEnabledProperty, true, false); + TestProperty(DataGrid.RowHeightProperty, 42, 44); + TestProperty(DataGrid.RowsBackgroundColorPaletteProperty, Palette1, Palette2); + TestProperty(DataGrid.RowsTextColorPaletteProperty, Palette1, Palette2); + TestProperty(DataGrid.SelectionModeProperty, SelectionMode.Single, SelectionMode.Multiple); + } + + private void Command1() { } + private void Command2() { } + + internal static void TestProperty(BindableProperty property, T testValue, T updatedValue) + { + var dataGrid = new DataGrid(); + dataGrid.CheckPropertyBindingWorks(property, testValue, updatedValue); + + var anotherDataGrid = new DataGrid(); + anotherDataGrid.CheckStyleSettingWorks(property, testValue); + } + +} diff --git a/Maui.DataGrid.Sample/Tests/TestUtils/SingleVM.cs b/Maui.DataGrid.Sample/Tests/TestUtils/SingleVM.cs new file mode 100644 index 0000000..aa8fde1 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/TestUtils/SingleVM.cs @@ -0,0 +1,20 @@ +namespace Maui.DataGrid.Sample.Tests.TestUtils; +using System.ComponentModel; + +internal sealed class SingleVM : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + private T _item; + public T Item + { + get => _item; + set + { + _item = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Item))); + } + } + + internal int NumberOfSubscribers => PropertyChanged?.GetInvocationList()?.Length ?? 0; +} diff --git a/Maui.DataGrid.Sample/Tests/TestUtils/TestExtensions.cs b/Maui.DataGrid.Sample/Tests/TestUtils/TestExtensions.cs new file mode 100644 index 0000000..c4b9399 --- /dev/null +++ b/Maui.DataGrid.Sample/Tests/TestUtils/TestExtensions.cs @@ -0,0 +1,69 @@ +namespace Maui.DataGrid.Sample.Tests.TestUtils; + +using System.Threading.Tasks; +using Microsoft.Maui.Controls; +using Xunit; + +internal static class TestExtensions +{ + public static async Task GetValueSafe(this BindableObject bindableObject, BindableProperty property) + { + if (Application.Current!.Dispatcher.IsDispatchRequired) + { + return await Application.Current.Dispatcher.DispatchAsync(() => bindableObject.GetValue(property)); + } + return bindableObject.GetValue(property); + } + + public static async void CheckPropertyBindingWorks(this BindableObject bindableObject, BindableProperty property, T testValue, T updatedValue) + { + Assert.Equal(property.DefaultValue, await bindableObject.GetValueSafe(property)); + + var viewModel = new SingleVM { Item = testValue }; + bindableObject.SetBinding(property, new Binding(nameof(SingleVM.Item), source: viewModel)); + + Assert.Equal(1, viewModel.NumberOfSubscribers); + Assert.Equal(testValue, await bindableObject.GetValueSafe(property)); + + var propertyChangedEventTriggered = false; + bindableObject.PropertyChanged += (s, e) => + { + if (e.PropertyName == property.PropertyName) + { + propertyChangedEventTriggered = true; + } + }; + + viewModel.Item = updatedValue; + Assert.Equal(updatedValue, await bindableObject.GetValueSafe(property)); + Assert.True(propertyChangedEventTriggered); + } + + public static async void DispatchIfRequired(this BindableObject bindableObject, Action action) + { + if (bindableObject.Dispatcher.IsDispatchRequired) + { + await bindableObject.Dispatcher.DispatchAsync(action); + } + else + { + action(); + } + } + + internal static void CheckStyleSettingWorks(this NavigableElement element, BindableProperty property, T value) + { + var style = new Style(element.GetType()) + { + Setters ={ + new Setter() {Property = property, Value = value } + } + }; + + element.DispatchIfRequired(() => + { + element.Style = style; + Assert.Equal(value, element.GetValue(property)); + }); + } +} diff --git a/Maui.DataGrid/DataGrid.xaml.cs b/Maui.DataGrid/DataGrid.xaml.cs index 684a1ed..68b7f1e 100644 --- a/Maui.DataGrid/DataGrid.xaml.cs +++ b/Maui.DataGrid/DataGrid.xaml.cs @@ -1,3 +1,9 @@ + +#if TEST +using System.Runtime.CompilerServices; +[assembly: InternalsVisibleTo("Maui.DataGrid.Sample")] +#endif + namespace Maui.DataGrid; using System.Collections; diff --git a/Maui.DataGrid/Maui.DataGrid.csproj b/Maui.DataGrid/Maui.DataGrid.csproj index 181e78e..9539071 100644 --- a/Maui.DataGrid/Maui.DataGrid.csproj +++ b/Maui.DataGrid/Maui.DataGrid.csproj @@ -37,6 +37,7 @@ README.md 4.0.0.1 4.0.1 + Debug;Release;Test