Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@
"dotnet restore": true,
"git checkout": true,
"Test-Path": true,
"git rev-parse": true
"git rev-parse": true,
"/^pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/vscode/Invoke-VSBuild\\.ps1 -SolutionPath TaskMaster\\.sln -Configuration Debug -Platform 'Any CPU' -EnableNETAnalyzers -EnforceCodeStyleInBuild$/": {
"approve": true,
"matchCommandLine": true
},
"/^pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/vscode/Invoke-VSBuild\\.ps1 -SolutionPath TaskMaster\\.sln -Configuration Debug -Platform 'Any CPU' -EnableNullable -TreatWarningsAsErrors$/": {
"approve": true,
"matchCommandLine": true
}
},
"koverage.coverageFileNames": [
"coverage.cobertura.xml"
Expand Down
68 changes: 68 additions & 0 deletions TaskMaster.Test/AppGlobals/AppOlObjectsTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
using FluentAssertions;
using Microsoft.Office.Interop.Outlook;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using UtilitiesCS;
using UtilitiesCS.Dialogs;
using OutlookApplication = Microsoft.Office.Interop.Outlook.Application;

namespace TaskMaster.Test.AppGlobals
{
Expand Down Expand Up @@ -126,5 +132,67 @@ public void WriteJunkPotentialSetting_UpdatesJunkPotentialValue()
Properties.Settings.Default.JunkPotential = original;
}
}

[TestMethod]
public void LoadJunkCertain_KeepsStoredValue_WhenReplacementSelectionIsCancelled()
{
var originalSetting = Properties.Settings.Default.OlJunkCertain;
var dialogInvokerProperty = typeof(MyBox).GetProperty(
"DialogInvoker",
BindingFlags.Static | BindingFlags.NonPublic
)!;
var originalDialogInvoker = dialogInvokerProperty.GetValue(null);
var expected = "Missing\\Junk Email";
var namespaceMapi = mockRepository.Create<NameSpace>();
namespaceMapi.Setup(x => x.PickFolder()).Returns((MAPIFolder)null);
var application = mockRepository.Create<OutlookApplication>();
application.SetupGet(x => x.Application).Returns(application.Object);
application.Setup(x => x.GetNamespace("MAPI")).Returns(namespaceMapi.Object);
var root = CreateRootFolder();
var sut = new AppOlObjects(application.Object, Mock.Of<IApplicationGlobals>());
SetPrivateField(sut, "_root", root.Object);

try
{
Properties.Settings.Default.OlJunkCertain = expected;
dialogInvokerProperty.SetValue(
null,
new Func<MyBoxViewer, System.Windows.Forms.DialogResult>(_ =>
System.Windows.Forms.DialogResult.OK
)
);

sut.LoadJunkCertain().Should().BeNull();
AppOlObjects.ReadJunkCertainSetting().Should().Be(expected);
}
finally
{
Properties.Settings.Default.OlJunkCertain = originalSetting;
dialogInvokerProperty.SetValue(null, originalDialogInvoker);
}
}

private Mock<Folder> CreateRootFolder()
{
var folders = mockRepository.Create<Folders>();
folders.SetupGet(x => x.Count).Returns(0);
folders
.As<IEnumerable>()
.Setup(x => x.GetEnumerator())
.Returns(Array.Empty<MAPIFolder>().GetEnumerator());

var root = mockRepository.Create<Folder>();
root.SetupGet(x => x.Name).Returns("Mailbox");
root.SetupGet(x => x.FolderPath).Returns(@"\\Mailbox");
root.SetupGet(x => x.Folders).Returns(folders.Object);
return root;
}

private static void SetPrivateField(object target, string fieldName, object value)
{
typeof(AppOlObjects)
.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic)!
.SetValue(target, value);
}
}
}
1 change: 1 addition & 0 deletions TaskMaster.Test/TaskMaster.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@
<Reference Include="System.ValueTuple" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
Expand Down
31 changes: 28 additions & 3 deletions TaskMaster/AppGlobals/AppOlObjects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,37 @@ public string InboxPath
private Folder _junkPotential;
public Folder JunkPotential => Initializer.GetOrLoad(ref _junkPotential, LoadJunkPotential);

internal static string ReadJunkCertainSetting() =>
Properties.Settings.Default.OlJunkCertain;

internal static void WriteJunkCertainSetting(string relativePath) =>
Properties.Settings.Default.OlJunkCertain = relativePath;

internal static string ReadJunkPotentialSetting() =>
Properties.Settings.Default.JunkPotential;

internal static void WriteJunkPotentialSetting(string relativePath) =>
Properties.Settings.Default.JunkPotential = relativePath;

internal void ApplyJunkFolderSelections(
string junkCertainRelativePath,
string junkPotentialRelativePath
)
{
WriteJunkCertainSetting(junkCertainRelativePath);
WriteJunkPotentialSetting(junkPotentialRelativePath);
Properties.Settings.Default.Save();
RefreshJunkFolderSelections();
}

internal void RefreshJunkFolderSelections()
{
_junkCertain = null;
_junkPotential = null;
_ = JunkCertain;
_ = JunkPotential;
}

internal Folder LoadJunkPotential()
{
var root = new FolderTree(Root).Roots.FirstOrDefault();
Expand Down Expand Up @@ -250,7 +275,7 @@ internal Folder LoadJunkPotential()
internal Folder LoadJunkCertain()
{
var root = new FolderTree(Root).Roots.FirstOrDefault();
var folderPath = Properties.Settings.Default.OlJunkCertain;
var folderPath = ReadJunkCertainSetting();
if (folderPath.IsNullOrEmpty())
{
return null;
Expand All @@ -262,7 +287,7 @@ internal Folder LoadJunkCertain()
if (folder is null)
{
MyBox.ShowDialog(
"Junk Potential Folder not found. Please select it manually.",
"Junk Folder not found. Please select it manually.",
"Error",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Error
Expand All @@ -273,7 +298,7 @@ internal Folder LoadJunkCertain()
return null;
}
var wrapper = new FolderWrapper(folder, Root);
Properties.Settings.Default.OlJunkCertain = wrapper.RelativePath;
WriteJunkCertainSetting(wrapper.RelativePath);
Properties.Settings.Default.Save();
}
return folder;
Expand Down
6 changes: 6 additions & 0 deletions TaskMaster/Ribbon/RibbonExplorer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@
label="Current Location"
/>
</menu>
<button
id="SpamFolderSettings"
imageMso="Folder"
onAction="SpamFolderSettings_Click"
label="Junk Folder Settings"
/>
<checkBox
id="SpamBayesEnabledToggle"
getPressed="SpamBayesEnabled_GetPressed"
Expand Down
3 changes: 3 additions & 0 deletions TaskMaster/Ribbon/RibbonViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ public async void SpamSaveLocal_Click(Office.IRibbonControl control) =>
public void GetSaveLocation_Click(Office.IRibbonControl control) =>
Controller.Engines.ShowSaveInfo(SpamBayes.GroupName);

public void SpamFolderSettings_Click(Office.IRibbonControl control) =>
Controller.FolderStoresSettings();

#endregion Spam Config

#endregion Spam Manager
Expand Down
186 changes: 186 additions & 0 deletions UtilitiesCS.Test/OutlookObjects/Store/StoreWrapperControllerTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Office.Interop.Outlook;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using UtilitiesCS;
using UtilitiesCS.OutlookObjects.Folder;
using UtilitiesCS.OutlookObjects.Store;
using OutlookFolder = Microsoft.Office.Interop.Outlook.Folder;

namespace UtilitiesCS.Test.OutlookObjects.Store
{
Expand All @@ -18,5 +28,181 @@ public void Controller_leaves_state_unchanged_when_dialog_is_cancelled()
{
StoreWrapperController.RunFolderSelectionDialog(() => false).Should().BeFalse();
}

[TestMethod]
public void PopulateWithCurrent_ShowsCurrentJunkSelectionsInViewer()
{
using var viewer = new StoreWrapperViewer();
var controller = new StoreWrapperController(null!) { Viewer = viewer };
controller.Current = new StoreWrapper(null)
{
JunkCertain = new FolderMinimalWrapper("Junk", "Inbox\\Junk Email"),
JunkPotential = new FolderMinimalWrapper("Potential", "Inbox\\Junk Potential"),
};

controller.PopulateWithCurrent();

viewer.JunkEmail.Text.Should().Be("Inbox\\Junk Email");
viewer.JunkPotential.Text.Should().Be("Inbox\\Junk Potential");
}

[TestMethod]
public void SaveChanges_PersistsBothSettingsAndRefreshesActiveJunkFolders()
{
var olObjects = new RecordingOlObjects();
var globals = new Mock<IApplicationGlobals>();
globals.SetupGet(x => x.Ol).Returns(olObjects);
var controller = new StoreWrapperController(globals.Object)
{
Model = new StoresWrapper(),
Current = new StoreWrapper(null),
JunkEmail = new FolderMinimalWrapper("Junk", "Inbox\\Junk Email"),
JunkPotential = new FolderMinimalWrapper("Potential", "Inbox\\Junk Potential"),
};

controller.SaveChanges();

controller.Current.JunkCertain.RelativePath.Should().Be("Inbox\\Junk Email");
controller.Current.JunkPotential.RelativePath.Should().Be("Inbox\\Junk Potential");
olObjects.ApplyCallCount.Should().Be(1);
olObjects.AppliedJunkCertainPath.Should().Be("Inbox\\Junk Email");
olObjects.AppliedJunkPotentialPath.Should().Be("Inbox\\Junk Potential");
olObjects.JunkCertain.FolderPath.Should().Be("Inbox\\Junk Email");
olObjects.JunkPotential.FolderPath.Should().Be("Inbox\\Junk Potential");
}

[TestMethod]
public void ButtonCancel_Click_LeavesStoredSettingsAndActiveFoldersUnchanged()
{
var olObjects = new RecordingOlObjects();
olObjects.ApplyJunkFolderSelections("Inbox\\Current Junk", "Inbox\\Current Potential");
var currentCertain = olObjects.JunkCertain;
var currentPotential = olObjects.JunkPotential;
var globals = new Mock<IApplicationGlobals>();
globals.SetupGet(x => x.Ol).Returns(olObjects);
var viewer = new Mock<IStoreWrapperViewer>();
var controller = new StoreWrapperController(globals.Object)
{
Viewer = viewer.Object,
Current = new StoreWrapper(null)
{
JunkCertain = new FolderMinimalWrapper("Current", "Inbox\\Current Junk"),
JunkPotential = new FolderMinimalWrapper(
"CurrentPotential",
"Inbox\\Current Potential"
),
},
JunkEmail = new FolderMinimalWrapper("New", "Inbox\\New Junk"),
JunkPotential = new FolderMinimalWrapper("NewPotential", "Inbox\\New Potential"),
};

controller.ButtonCancel_Click();

controller.Current.JunkCertain.RelativePath.Should().Be("Inbox\\Current Junk");
controller.Current.JunkPotential.RelativePath.Should().Be("Inbox\\Current Potential");
olObjects.ApplyCallCount.Should().Be(1);
olObjects.JunkCertain.Should().BeSameAs(currentCertain);
olObjects.JunkPotential.Should().BeSameAs(currentPotential);
viewer.Verify(x => x.Close(), Times.Once);
}

[TestMethod]
public void PopulateWithCurrent_WhenInvokeRequired_DelegatesToViewerInvoke()
{
var viewer = new Mock<IStoreWrapperViewer>();
viewer.SetupGet(x => x.InvokeRequired).Returns(true);
viewer.Setup(x => x.Invoke(It.IsAny<Delegate>())).Returns((object)null);
var controller = new StoreWrapperController(null!) { Viewer = viewer.Object };

controller.PopulateWithCurrent();

viewer.Verify(x => x.Invoke(It.IsAny<Delegate>()), Times.Once);
}

[TestMethod]
public void PersistJunkFolderSelections_WhenApplyMethodIsMissing_DoesNotThrow()
{
var globals = new Mock<IApplicationGlobals>();
globals.SetupGet(x => x.Ol).Returns(new NoApplyOlObjects());
var controller = new StoreWrapperController(globals.Object)
{
JunkEmail = new FolderMinimalWrapper("Junk", "Inbox\\Junk Email"),
JunkPotential = new FolderMinimalWrapper("Potential", "Inbox\\Junk Potential"),
};

var act = () => controller.PersistJunkFolderSelections();

act.Should().NotThrow();
}

private abstract class OlObjectsStubBase : IOlObjects
{
public Application App => null!;
public string InboxPath => string.Empty;
public string ArchiveRootPath => string.Empty;
public OutlookFolder ArchiveRoot => null!;
public string UserEmailAddress => string.Empty;
public string EmailPrefixToStrip => string.Empty;
public OutlookFolder Inbox => null!;
public IEnumerable<OutlookFolder> Inboxes => [];
public NameSpace NamespaceMAPI => null!;
public OutlookFolder Root => null!;
public StoresWrapper StoresWrapper { get; set; } = new();
public Reminders OlReminders => null!;
public OutlookFolder ToDoFolder => null!;
public StackObjectCS<object> MovedMailsStack { get; set; } = null!;
public string ViewWide => string.Empty;
public string ViewCompact => string.Empty;
public bool DarkMode { get; set; }
public TimedDiskWriter<string> EmailMoveWriter => null!;
public OutlookFolder JunkCertain { get; private set; } = null!;
public OutlookFolder JunkPotential { get; private set; } = null!;

public int GetExplorerScreenNumber() => 0;

public System.Windows.Forms.Screen GetExplorerScreen() => null!;

public Size GetExplorerScreenSize() => default;

public Task LoadAsync() => Task.CompletedTask;

public event PropertyChangedEventHandler PropertyChanged;

protected static OutlookFolder CreateFolder(string folderPath)
{
var folder = new Mock<OutlookFolder>();
folder.SetupGet(x => x.FolderPath).Returns(folderPath);
return folder.Object;
}

protected void SetJunkFolders(
string junkCertainRelativePath,
string junkPotentialRelativePath
)
{
JunkCertain = CreateFolder(junkCertainRelativePath);
JunkPotential = CreateFolder(junkPotentialRelativePath);
}
}

private sealed class RecordingOlObjects : OlObjectsStubBase
{
public string AppliedJunkCertainPath { get; private set; } = string.Empty;
public string AppliedJunkPotentialPath { get; private set; } = string.Empty;
public int ApplyCallCount { get; private set; }

public void ApplyJunkFolderSelections(
string junkCertainRelativePath,
string junkPotentialRelativePath
)
{
AppliedJunkCertainPath = junkCertainRelativePath;
AppliedJunkPotentialPath = junkPotentialRelativePath;
ApplyCallCount++;
SetJunkFolders(junkCertainRelativePath, junkPotentialRelativePath);
}
}

private sealed class NoApplyOlObjects : OlObjectsStubBase { }
}
}
Loading
Loading