Some checks failed
Release SharePoint Toolbox v2 / release (push) Failing after 14s
v1.1 shipped with 4 phases (25 plans), 10/10 requirements complete: - Global site selection (toolbar picker, all tabs consume) - User access audit (Graph people-picker, direct/group/inherited) - Simplified permissions (plain-language labels, risk levels, detail toggle) - Storage visualization (LiveCharts2 pie/donut + bar charts) Post-phase polish: centralized site selection (removed per-tab pickers), claims prefix stripping, StorageMetrics backfill, chart tooltip fix, summary stats in app + HTML exports. 205 tests passing, 10,484 LOC. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
218 lines
7.0 KiB
C#
218 lines
7.0 KiB
C#
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<double>>().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<double>>().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);
|
|
}
|
|
}
|