diff --git a/SPToolbox-logo-ico.png b/SPToolbox-logo-ico.png new file mode 100644 index 0000000..2955eef Binary files /dev/null and b/SPToolbox-logo-ico.png differ diff --git a/SharepointToolbox.Tests/Services/StorageServiceTests.cs b/SharepointToolbox.Tests/Services/StorageServiceTests.cs index ed8e3a7..f2b4e71 100644 --- a/SharepointToolbox.Tests/Services/StorageServiceTests.cs +++ b/SharepointToolbox.Tests/Services/StorageServiceTests.cs @@ -28,4 +28,21 @@ public class StorageServiceTests var node = new StorageNode { TotalSizeBytes = 5000L, FileStreamSizeBytes = 3000L }; Assert.Equal(2000L, node.VersionSizeBytes); } + + [Fact] + public void StorageNode_DefaultKind_IsLibrary() + { + var node = new StorageNode(); + Assert.Equal(StorageNodeKind.Library, node.Kind); + } + + [Fact] + public void StorageScanOptions_DefaultIncludeFlags_AreAllTrue() + { + var opts = new StorageScanOptions(); + Assert.True(opts.IncludeHiddenLibraries); + Assert.True(opts.IncludePreservationHold); + Assert.True(opts.IncludeListAttachments); + Assert.True(opts.IncludeRecycleBin); + } } diff --git a/SharepointToolbox.Tests/ViewModels/StorageViewModelFilterTests.cs b/SharepointToolbox.Tests/ViewModels/StorageViewModelFilterTests.cs new file mode 100644 index 0000000..7c8f7db --- /dev/null +++ b/SharepointToolbox.Tests/ViewModels/StorageViewModelFilterTests.cs @@ -0,0 +1,141 @@ +using System.Reflection; +using CommunityToolkit.Mvvm.Messaging; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using SharepointToolbox.Core.Models; +using SharepointToolbox.Services; +using SharepointToolbox.ViewModels; +using SharepointToolbox.ViewModels.Tabs; +using Xunit; + +namespace SharepointToolbox.Tests.ViewModels; + +/// +/// Verifies that the report filter flags (Show*) project the raw scan output +/// (_allNodes) into Results correctly: hiding a kind drops the +/// matching root nodes plus their entire subtree, preserving DFS ordering. +/// +public class StorageViewModelFilterTests +{ + public StorageViewModelFilterTests() => WeakReferenceMessenger.Default.Reset(); + + private static StorageViewModel CreateVm() + { + var vm = new StorageViewModel( + new Mock().Object, + new Mock().Object, + NullLogger.Instance); + vm.SetCurrentProfile(new TenantProfile { Name = "T", TenantUrl = "https://t", ClientId = "c" }); + return vm; + } + + /// Inject a flat node list straight into the private _allNodes field + /// and trigger a rebuild via toggling a Show flag. + private static void Seed(StorageViewModel vm, List flat) + { + var field = typeof(StorageViewModel).GetField("_allNodes", + BindingFlags.Instance | BindingFlags.NonPublic)!; + field.SetValue(vm, flat); + // Toggle off+on to force RebuildFilteredResults(). + vm.ShowLibraries = false; + vm.ShowLibraries = true; + } + + private static List MakeMixedTree() => new() + { + new() { Name = "Documents", Kind = StorageNodeKind.Library, IndentLevel = 0, TotalSizeBytes = 100 }, + new() { Name = "Sub", Kind = StorageNodeKind.Library, IndentLevel = 1, TotalSizeBytes = 50 }, + new() { Name = "Preserve", Kind = StorageNodeKind.PreservationHold, IndentLevel = 0, TotalSizeBytes = 200 }, + new() { Name = "[Recycle]", Kind = StorageNodeKind.RecycleBin, IndentLevel = 0, TotalSizeBytes = 300 }, + new() { Name = "[Attach] L",Kind = StorageNodeKind.ListAttachments, IndentLevel = 0, TotalSizeBytes = 75 }, + }; + + [Fact] + public void AllShowFlagsTrue_AllNodesAppear() + { + var vm = CreateVm(); + Seed(vm, MakeMixedTree()); + Assert.Equal(5, vm.Results.Count); + } + + [Fact] + public void HideRecycleBin_RemovesOnlyRecycleNode() + { + var vm = CreateVm(); + Seed(vm, MakeMixedTree()); + vm.ShowRecycleBin = false; + Assert.DoesNotContain(vm.Results, n => n.Kind == StorageNodeKind.RecycleBin); + Assert.Equal(4, vm.Results.Count); + } + + [Fact] + public void HidePreservationHold_RemovesPreservationNode() + { + var vm = CreateVm(); + Seed(vm, MakeMixedTree()); + vm.ShowPreservationHold = false; + Assert.DoesNotContain(vm.Results, n => n.Kind == StorageNodeKind.PreservationHold); + } + + [Fact] + public void HideLibraries_DropsLibraryRootAndItsChildren() + { + var vm = CreateVm(); + Seed(vm, MakeMixedTree()); + vm.ShowLibraries = false; + // "Documents" + "Sub" both gone — Sub's subtree dropped with its parent root. + Assert.DoesNotContain(vm.Results, n => n.Name == "Documents"); + Assert.DoesNotContain(vm.Results, n => n.Name == "Sub"); + } + + [Fact] + public void CombineRecycleBinStages_True_MergesStagesIntoSingleRow() + { + var vm = CreateVm(); + var nodes = new List + { + new() { Name = "[Recycle Bin] First-stage", Kind = StorageNodeKind.RecycleBin, IndentLevel = 0, + SiteTitle = "S1", TotalSizeBytes = 100, FileStreamSizeBytes = 100, TotalFileCount = 3 }, + new() { Name = "[Recycle Bin] Second-stage", Kind = StorageNodeKind.RecycleBin, IndentLevel = 0, + SiteTitle = "S1", TotalSizeBytes = 250, FileStreamSizeBytes = 250, TotalFileCount = 7 }, + }; + Seed(vm, nodes); + vm.CombineRecycleBinStages = true; + + var bins = vm.Results.Where(n => n.Kind == StorageNodeKind.RecycleBin).ToList(); + Assert.Single(bins); + Assert.Equal(350, bins[0].TotalSizeBytes); + Assert.Equal(10, bins[0].TotalFileCount); + } + + [Fact] + public void CombineRecycleBinStages_False_KeepsSeparateRows() + { + var vm = CreateVm(); + var nodes = new List + { + new() { Name = "[Recycle Bin] First-stage", Kind = StorageNodeKind.RecycleBin, IndentLevel = 0, + SiteTitle = "S1", TotalSizeBytes = 100 }, + new() { Name = "[Recycle Bin] Second-stage", Kind = StorageNodeKind.RecycleBin, IndentLevel = 0, + SiteTitle = "S1", TotalSizeBytes = 250 }, + }; + Seed(vm, nodes); + vm.CombineRecycleBinStages = false; + + Assert.Equal(2, vm.Results.Count(n => n.Kind == StorageNodeKind.RecycleBin)); + } + + [Fact] + public void HideAll_LeavesEmptyResults() + { + var vm = CreateVm(); + Seed(vm, MakeMixedTree()); + vm.ShowLibraries = false; + vm.ShowHiddenLibraries = false; + vm.ShowPreservationHold = false; + vm.ShowListAttachments = false; + vm.ShowRecycleBin = false; + vm.ShowSubsites = false; + Assert.Empty(vm.Results); + } +} diff --git a/SharepointToolbox/App.xaml b/SharepointToolbox/App.xaml index 5d96c6a..96a5640 100644 --- a/SharepointToolbox/App.xaml +++ b/SharepointToolbox/App.xaml @@ -16,6 +16,7 @@ +