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:
@@ -11,6 +11,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||||
|
<PackageReference Include="LiveChartsCore.SkiaSharpView.WPF" Version="2.0.0-rc5.4" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
|||||||
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