Skip to content

Commit

Permalink
Merge pull request #1 from intcooper/feature/mvvm
Browse files Browse the repository at this point in the history
Feature/mvvm
  • Loading branch information
rguida authored May 17, 2024
2 parents 7089975 + 8edc118 commit c58aa05
Show file tree
Hide file tree
Showing 29 changed files with 642 additions and 169 deletions.
Binary file added Docs/Images/MMExNotifier-MainWindow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions MMExNotifier.Tests/MMExNotifier.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0-windows10.0.22000.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="3.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MMExNotifier\MMExNotifier.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="NUnit.Framework" />
</ItemGroup>

</Project>
124 changes: 124 additions & 0 deletions MMExNotifier.Tests/MainViewModelTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@

using MMExNotifier.Database;
using MMExNotifier.DataModel;
using MMExNotifier.Helpers;
using MMExNotifier.ViewModels;
using Moq;

namespace MMExNotifier.Tests
{
public class MainViewModelTests
{
private Mock<IAppConfiguration> mockAppConfiguration;
private Mock<IDatabaseService> mockDatabaseService;
private Mock<INotificationService> mockNotificationService;

[SetUp]
public void Setup()
{
mockAppConfiguration = new Mock<IAppConfiguration>();
mockAppConfiguration.Setup(x => x.MMExDatabasePath).Returns("C:\\mydatabase.mmb");
mockAppConfiguration.Setup(x => x.RunAtLogon).Returns(false);
mockAppConfiguration.Setup(x => x.DaysAhead).Returns(7);

mockDatabaseService = new Mock<IDatabaseService>();

mockNotificationService = new Mock<INotificationService>();
}

[Test]
public void WhenDatabasePathIsNotConfigured_ShouldNotAttemptOpenDatabase()
{
mockAppConfiguration.Setup(x => x.MMExDatabasePath).Returns(string.Empty);
mockDatabaseService.Setup(x => x.ExpiringBills).Returns(() => null);

var mainViewModel = new MainViewModel(mockAppConfiguration.Object, mockNotificationService.Object, mockDatabaseService.Object);
mainViewModel.Activate();

Assert.That(mockDatabaseService.Invocations, Is.Empty);
}

[Test]
public void WhenNoExpiringBills_ShouldRaiseCloseEvent()
{
mockDatabaseService.Setup(x => x.ExpiringBills).Returns(() => null);

var mainViewModel = new MainViewModel(mockAppConfiguration.Object, mockNotificationService.Object, mockDatabaseService.Object);
var closeInvoked = false;
mainViewModel.OnClose += (s, e) => { closeInvoked = true; };
mainViewModel.Activate();

Assert.That(closeInvoked, Is.True);
}

[Test]
public void WhenAtLeastOneExpiringBill_ShouldShowToastNotification()
{
mockDatabaseService.Setup(x => x.ExpiringBills).Returns(
new List<ExpiringBill>
{
new()
{
BillId=1,
CategoryName="testCategory",
PayeeName="TestPayee",
NextOccurrenceDate=new DateTime(2024,6,1)
}
});

mockNotificationService.Setup(
x => x.ShowToastNotification("viewTransactions", It.IsAny<int>(), "MMExNotifier", "One ore more recurring transaction are about to expire.", It.IsAny<Action>())
);

var mainViewModel = new MainViewModel(mockAppConfiguration.Object, mockNotificationService.Object, mockDatabaseService.Object);
mainViewModel.Activate();

mockNotificationService.Verify();
}

[Test]
public void WhenNotificationActivated_ShouldRaiseOpen()
{
mockDatabaseService.Setup(x => x.ExpiringBills).Returns(
new List<ExpiringBill>
{
new()
{
BillId=1,
CategoryName="testCategory",
PayeeName="TestPayee",
NextOccurrenceDate=new DateTime(2024,6,1)
}
});

var mockToastNotification = new Mock<IToastNotification>();
mockToastNotification.Setup(
x => x.Show(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<string>(), It.IsAny<string>())
);

var mainViewModel = new MainViewModel(mockAppConfiguration.Object, new NotificationService(mockToastNotification.Object), mockDatabaseService.Object);
var openInvoked = false;
mainViewModel.OnOpen += (s, e) => { openInvoked = true; };
mainViewModel.Activate();

mockToastNotification.Raise(x => x.OnActivated += null, new EventArgs());

Assert.That(openInvoked, Is.True);
}

[Test]
public void OnDatabaseError_ShouldShowErrorMessage()
{
mockDatabaseService.Setup(x => x.ExpiringBills).Throws<InvalidOperationException>();

mockNotificationService.Setup(
x => x.ShowErrorNotification(It.IsAny<string>())
);

var mainViewModel = new MainViewModel(mockAppConfiguration.Object, mockNotificationService.Object, mockDatabaseService.Object);
mainViewModel.Activate();

mockNotificationService.Verify();
}
}
}
8 changes: 7 additions & 1 deletion MMExNotifier.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MMExNotifier", "MMExNotifier\MMExNotifier.csproj", "{91E2037D-BD50-4051-AA06-C8CC19D00131}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MMExNotifier", "MMExNotifier\MMExNotifier.csproj", "{91E2037D-BD50-4051-AA06-C8CC19D00131}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MMExNotifier.Tests", "MMExNotifier.Tests\MMExNotifier.Tests.csproj", "{8E1F80AF-5A10-4790-8367-A15EC5A0C2B2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,6 +17,10 @@ Global
{91E2037D-BD50-4051-AA06-C8CC19D00131}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91E2037D-BD50-4051-AA06-C8CC19D00131}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91E2037D-BD50-4051-AA06-C8CC19D00131}.Release|Any CPU.Build.0 = Release|Any CPU
{8E1F80AF-5A10-4790-8367-A15EC5A0C2B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E1F80AF-5A10-4790-8367-A15EC5A0C2B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E1F80AF-5A10-4790-8367-A15EC5A0C2B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E1F80AF-5A10-4790-8367-A15EC5A0C2B2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
49 changes: 11 additions & 38 deletions MMExNotifier/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Microsoft.Toolkit.Uwp.Notifications;
using System.Linq;
using MMExNotifier.Database;
using MMExNotifier.DataModel;
using MMExNotifier.Helpers;
using MMExNotifier.ViewModels;
using System.Windows;
using Windows.Foundation.Collections;

namespace MMExNotifier
{
Expand All @@ -14,43 +15,15 @@ protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

var dbPath = MMExNotifier.Properties.Settings.Default.MMExDatabasePath;
var appConfiguration = new AppConfiguration();

if (string.IsNullOrEmpty(dbPath))
{
var mainWindow = new MainWindow();
return;
}
var view = new MainWindow();
var viewModel = new MainViewModel(appConfiguration, new NotificationService(new ToastNotification()), new DatabaseService(appConfiguration));

var daysAhead = MMExNotifier.Properties.Settings.Default.DaysAhead;
var expiringTransactions = DbHelper.LoadRecurringTransactions(dbPath, daysAhead);

if ((expiringTransactions != null) && (!expiringTransactions.Any()))
{
App.Current.Shutdown(0);
}
else
{
new ToastContentBuilder()
.AddArgument("action", "viewTransactions")
.AddArgument("conversationId", 9813)
.AddText($"MMExNotifier", AdaptiveTextStyle.Header)
.AddText($"One ore more recurring transaction are about to expire.")
.SetToastScenario(ToastScenario.Reminder)
.Show();

// Listen to notification activation
ToastNotificationManagerCompat.OnActivated += toastArgs =>
{
ToastArguments args = ToastArguments.Parse(toastArgs.Argument);
ValueSet userInput = toastArgs.UserInput;
Application.Current.Dispatcher.Invoke(delegate
{
var mainWindow = new MainWindow();
mainWindow.ShowDialog();
});
};
}
view.DataContext = viewModel;
viewModel.OnClose += (s, e) => Application.Current.Dispatcher.Invoke(() => view.Close());
viewModel.OnOpen += (s, e) => Application.Current.Dispatcher.Invoke(() => { if (view.Visibility != Visibility.Visible) view.ShowDialog(); });
viewModel.Activate();
}
}
}
4 changes: 4 additions & 0 deletions MMExNotifier/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using System.Windows;

[assembly: ThemeInfo(
Expand All @@ -8,3 +9,6 @@
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

[assembly: InternalsVisibleTo("MMExNotifier.Tests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
13 changes: 0 additions & 13 deletions MMExNotifier/Connection.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using LinqToDB.Mapping;
using System;

namespace MMExNotifier.Entities
namespace MMExNotifier.DataModel
{
[Table(Name = "ACCOUNTLIST_V1")]
internal class Account
Expand Down
71 changes: 71 additions & 0 deletions MMExNotifier/DataModel/AppConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Microsoft.Win32.TaskScheduler;
using System;
using System.Linq;
using System.Security.Principal;

namespace MMExNotifier.DataModel
{
internal class AppConfiguration : IAppConfiguration
{
public string? MMExDatabasePath { get; set; }
public int DaysAhead { get; set; }
public bool RunAtLogon { get; set; }

public AppConfiguration()
{
MMExDatabasePath = Properties.Settings.Default.MMExDatabasePath;
DaysAhead = Properties.Settings.Default.DaysAhead;

using TaskService taskService = new();
RunAtLogon = taskService.RootFolder.Tasks.Any(t => t.Name == "MMExNotifier");
}

public void Save()
{
Properties.Settings.Default.DaysAhead = DaysAhead;
Properties.Settings.Default.MMExDatabasePath = MMExDatabasePath;

if (RunAtLogon)
{
EnableSchedulerTask();
}
else
{
DisableSchedulerTask();
}

Properties.Settings.Default.Save();
}

private static void EnableSchedulerTask()
{
using TaskService taskService = new();

if (taskService.RootFolder.Tasks.Any(t => t.Name == "MMExNotifier"))
return;

TaskDefinition taskDefinition = taskService.NewTask();

// Set the task settings
taskDefinition.RegistrationInfo.Description = "MMExNotifier";
var userId = WindowsIdentity.GetCurrent().Name;

// Set the trigger to run on logon
LogonTrigger logonTrigger = new() { UserId = userId };
taskDefinition.Triggers.Add(logonTrigger);

// Set the action to run the executable that creates the task
string executablePath = Environment.ProcessPath ?? "";
taskDefinition.Actions.Add(new ExecAction(executablePath));

// Register the task in the Windows Task Scheduler
taskService.RootFolder.RegisterTaskDefinition("MMExNotifier", taskDefinition);
}

private static void DisableSchedulerTask()
{
using TaskService taskService = new();
taskService.RootFolder.DeleteTask("MMExNotifier", false);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using LinqToDB.Mapping;
using System;

namespace MMExNotifier.Entities
namespace MMExNotifier.DataModel
{
[Table(Name = "BILLSDEPOSITS_V1")]
internal class BillDeposit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using LinqToDB.Mapping;

namespace MMExNotifier.Entities
namespace MMExNotifier.DataModel
{
[Table(Name="CATEGORY_V1")]
[Table(Name = "CATEGORY_V1")]
internal class Category
{
[PrimaryKey]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace MMExNotifier.Entities
namespace MMExNotifier.DataModel
{
public class ExpiringBill
{
Expand Down
11 changes: 11 additions & 0 deletions MMExNotifier/DataModel/IAppConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace MMExNotifier.DataModel
{
internal interface IAppConfiguration
{
int DaysAhead { get; set; }
string? MMExDatabasePath { get; set; }
bool RunAtLogon { get; set; }

void Save();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using LinqToDB.Mapping;

namespace MMExNotifier.Entities
namespace MMExNotifier.DataModel
{
[Table(Name="PAYEE_V1")]
[Table(Name = "PAYEE_V1")]
internal class Payee
{
[PrimaryKey]
Expand Down
Loading

0 comments on commit c58aa05

Please sign in to comment.