From 712b949eb20863ca0b8b7433afc6e5ecac0cbc4a Mon Sep 17 00:00:00 2001 From: Dev Date: Tue, 7 Apr 2026 15:40:26 +0200 Subject: [PATCH] test(09-04): add StorageViewModel chart unit tests - 7 tests covering chart series from metrics, bar series structure, donut/bar toggle, top-10+Other aggregation, no-Other for <=10, tenant switch cleanup, and empty data handling - Added LiveChartsCore.SkiaSharpView.WPF to test project - Uses reflection to set FileTypeMetrics (private setter) directly Co-Authored-By: Claude Opus 4.6 (1M context) --- .../SharepointToolbox.Tests.csproj | 1 + .../ViewModels/StorageViewModelChartTests.cs | 217 ++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 SharepointToolbox.Tests/ViewModels/StorageViewModelChartTests.cs diff --git a/SharepointToolbox.Tests/SharepointToolbox.Tests.csproj b/SharepointToolbox.Tests/SharepointToolbox.Tests.csproj index 500ab71..d870a37 100644 --- a/SharepointToolbox.Tests/SharepointToolbox.Tests.csproj +++ b/SharepointToolbox.Tests/SharepointToolbox.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/SharepointToolbox.Tests/ViewModels/StorageViewModelChartTests.cs b/SharepointToolbox.Tests/ViewModels/StorageViewModelChartTests.cs new file mode 100644 index 0000000..54ceb70 --- /dev/null +++ b/SharepointToolbox.Tests/ViewModels/StorageViewModelChartTests.cs @@ -0,0 +1,217 @@ +using System.Collections.ObjectModel; +using System.Reflection; +using CommunityToolkit.Mvvm.Messaging; +using LiveChartsCore; +using LiveChartsCore.SkiaSharpView; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using SharepointToolbox.Core.Messages; +using SharepointToolbox.Core.Models; +using SharepointToolbox.Services; +using SharepointToolbox.ViewModels; +using SharepointToolbox.ViewModels.Tabs; + +namespace SharepointToolbox.Tests.ViewModels; + +/// +/// Unit tests for StorageViewModel chart functionality (Phase 09 Plan 04). +/// Verifies: chart series from metrics, bar series structure, donut/bar toggle, +/// top-10 + Other aggregation, no-Other for <=10, tenant switch cleanup, empty data. +/// Uses reflection to set FileTypeMetrics directly, bypassing ClientContext dependency. +/// +public class StorageViewModelChartTests +{ + public StorageViewModelChartTests() + { + WeakReferenceMessenger.Default.Reset(); + } + + // -- Helper factories -------------------------------------------------------- + + private static StorageViewModel CreateViewModel() + { + var mockStorage = new Mock(); + var mockSession = new Mock(); + + var vm = new StorageViewModel( + mockStorage.Object, + mockSession.Object, + NullLogger.Instance); + + vm.SetCurrentProfile(new TenantProfile + { + Name = "Test", + TenantUrl = "https://test.sharepoint.com", + ClientId = "test-id" + }); + + return vm; + } + + /// + /// Sets FileTypeMetrics via the property (private setter) using reflection, + /// which also triggers UpdateChartSeries. + /// + private static void SetFileTypeMetrics(StorageViewModel vm, IList metrics) + { + var prop = typeof(StorageViewModel).GetProperty( + nameof(StorageViewModel.FileTypeMetrics), + BindingFlags.Public | BindingFlags.Instance); + prop!.SetValue(vm, new ObservableCollection(metrics)); + } + + private static List MakeMetrics(int count) + { + var extensions = new[] + { + ".docx", ".pdf", ".xlsx", ".pptx", ".jpg", + ".png", ".mp4", ".zip", ".csv", ".html", + ".txt", ".json", ".xml", ".msg", ".eml" + }; + + var metrics = new List(); + for (int i = 0; i < count; i++) + { + string ext = i < extensions.Length ? extensions[i] : $".ext{i}"; + metrics.Add(new FileTypeMetric(ext, (count - i) * 1024L * 1024, (count - i) * 10)); + } + return metrics; + } + + // -- Test 1: Chart series populated from metrics ----------------------------- + + [Fact] + public void After_setting_metrics_HasChartData_is_true_and_PieChartSeries_has_entries() + { + var vm = CreateViewModel(); + var metrics = MakeMetrics(5); + + SetFileTypeMetrics(vm, metrics); + + Assert.True(vm.HasChartData); + Assert.NotEmpty(vm.PieChartSeries); + Assert.Equal(5, vm.PieChartSeries.Count()); + } + + // -- Test 2: Bar series has one ColumnSeries with correct value count -------- + + [Fact] + public void After_setting_metrics_BarChartSeries_has_one_ColumnSeries_with_matching_values() + { + var vm = CreateViewModel(); + var metrics = MakeMetrics(5); + + SetFileTypeMetrics(vm, metrics); + + var barSeries = vm.BarChartSeries.ToList(); + Assert.Single(barSeries); + + var columnSeries = Assert.IsType>(barSeries[0]); + Assert.Equal(5, columnSeries.Values!.Count()); + } + + // -- Test 3: Toggle IsDonutChart changes PieChartSeries InnerRadius ---------- + + [Fact] + public void Toggle_IsDonutChart_changes_PieChartSeries_InnerRadius() + { + var vm = CreateViewModel(); + var metrics = MakeMetrics(3); + + SetFileTypeMetrics(vm, metrics); + + // Initially IsDonutChart=true => InnerRadius=50 + var pieBefore = vm.PieChartSeries.Cast>().ToList(); + Assert.All(pieBefore, s => Assert.Equal(50, s.InnerRadius)); + + // Toggle to bar (not donut) => InnerRadius=0 + vm.IsDonutChart = false; + + var pieAfter = vm.PieChartSeries.Cast>().ToList(); + Assert.All(pieAfter, s => Assert.Equal(0, s.InnerRadius)); + } + + // -- Test 4: More than 10 file types => 11 entries (10 + Other) -------------- + + [Fact] + public void More_than_10_metrics_produces_11_series_entries_with_Other() + { + var vm = CreateViewModel(); + var metrics = MakeMetrics(15); + + SetFileTypeMetrics(vm, metrics); + + // Pie series: 10 real + 1 "Other" = 11 + Assert.Equal(11, vm.PieChartSeries.Count()); + + // Last pie entry should be named "OTHER" (DisplayLabel uppercases extension) + var lastPie = vm.PieChartSeries.Last(); + Assert.Equal("OTHER", lastPie.Name); + + // Bar series column should have 11 values + var columnSeries = Assert.IsType>(vm.BarChartSeries.First()); + Assert.Equal(11, columnSeries.Values!.Count()); + + // X-axis should have 11 labels + Assert.Equal(11, vm.BarXAxes[0].Labels!.Count); + } + + // -- Test 5: 10 or fewer file types => no "Other" entry ---------------------- + + [Fact] + public void Ten_or_fewer_metrics_produces_no_Other_entry() + { + var vm = CreateViewModel(); + var metrics = MakeMetrics(10); + + SetFileTypeMetrics(vm, metrics); + + Assert.Equal(10, vm.PieChartSeries.Count()); + + // No entry named "OTHER" (DisplayLabel uppercases) + Assert.DoesNotContain(vm.PieChartSeries, s => s.Name == "OTHER"); + } + + // -- Test 6: Tenant switch clears chart data --------------------------------- + + [Fact] + public void OnTenantSwitched_clears_FileTypeMetrics_and_HasChartData_is_false() + { + var vm = CreateViewModel(); + var metrics = MakeMetrics(5); + + SetFileTypeMetrics(vm, metrics); + Assert.True(vm.HasChartData); + + // Act: send TenantSwitchedMessage + var newProfile = new TenantProfile + { + Name = "NewTenant", + TenantUrl = "https://newtenant.sharepoint.com", + ClientId = "new-id" + }; + WeakReferenceMessenger.Default.Send(new TenantSwitchedMessage(newProfile)); + + Assert.False(vm.HasChartData); + Assert.Empty(vm.FileTypeMetrics); + Assert.Empty(vm.PieChartSeries); + Assert.Empty(vm.BarChartSeries); + } + + // -- Test 7: Empty metrics => HasChartData false, series empty --------------- + + [Fact] + public void Empty_metrics_yields_HasChartData_false_and_empty_series() + { + var vm = CreateViewModel(); + + SetFileTypeMetrics(vm, new List()); + + Assert.False(vm.HasChartData); + Assert.Empty(vm.PieChartSeries); + Assert.Empty(vm.BarChartSeries); + Assert.Empty(vm.BarXAxes); + Assert.Empty(vm.BarYAxes); + } +}