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) <noreply@anthropic.com>
This commit is contained in:
217
SharepointToolbox.Tests/ViewModels/StorageViewModelChartTests.cs
Normal file
217
SharepointToolbox.Tests/ViewModels/StorageViewModelChartTests.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class StorageViewModelChartTests
|
||||
{
|
||||
public StorageViewModelChartTests()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Reset();
|
||||
}
|
||||
|
||||
// -- Helper factories --------------------------------------------------------
|
||||
|
||||
private static StorageViewModel CreateViewModel()
|
||||
{
|
||||
var mockStorage = new Mock<IStorageService>();
|
||||
var mockSession = new Mock<ISessionManager>();
|
||||
|
||||
var vm = new StorageViewModel(
|
||||
mockStorage.Object,
|
||||
mockSession.Object,
|
||||
NullLogger<FeatureViewModelBase>.Instance);
|
||||
|
||||
vm.SetCurrentProfile(new TenantProfile
|
||||
{
|
||||
Name = "Test",
|
||||
TenantUrl = "https://test.sharepoint.com",
|
||||
ClientId = "test-id"
|
||||
});
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets FileTypeMetrics via the property (private setter) using reflection,
|
||||
/// which also triggers UpdateChartSeries.
|
||||
/// </summary>
|
||||
private static void SetFileTypeMetrics(StorageViewModel vm, IList<FileTypeMetric> metrics)
|
||||
{
|
||||
var prop = typeof(StorageViewModel).GetProperty(
|
||||
nameof(StorageViewModel.FileTypeMetrics),
|
||||
BindingFlags.Public | BindingFlags.Instance);
|
||||
prop!.SetValue(vm, new ObservableCollection<FileTypeMetric>(metrics));
|
||||
}
|
||||
|
||||
private static List<FileTypeMetric> 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<FileTypeMetric>();
|
||||
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<ColumnSeries<long>>(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<PieSeries<long>>().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<PieSeries<long>>().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<ColumnSeries<long>>(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<FileTypeMetric>());
|
||||
|
||||
Assert.False(vm.HasChartData);
|
||||
Assert.Empty(vm.PieChartSeries);
|
||||
Assert.Empty(vm.BarChartSeries);
|
||||
Assert.Empty(vm.BarXAxes);
|
||||
Assert.Empty(vm.BarYAxes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user