diff --git a/RepoM.sln b/RepoM.sln index 6adf8a62..fa5cb397 100644 --- a/RepoM.sln +++ b/RepoM.sln @@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.Plugin.Clipboard.Test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.Plugin.EverythingFileSearch.Tests", "tests\RepoM.Plugin.EverythingFileSearch.Tests\RepoM.Plugin.EverythingFileSearch.Tests.csproj", "{89D96078-2951-44C2-B5B1-1DA0D5E94C0C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RepoM.Plugin.WindowsExplorerGitInfo.Tests", "tests\RepoM.Plugin.WindowsExplorerGitInfo.Tests\RepoM.Plugin.WindowsExplorerGitInfo.Tests.csproj", "{99DFA130-C0F4-4D1F-8428-5CACA481B688}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -481,6 +483,26 @@ Global {89D96078-2951-44C2-B5B1-1DA0D5E94C0C}.Release|x64.Build.0 = Release|Any CPU {89D96078-2951-44C2-B5B1-1DA0D5E94C0C}.Release|x86.ActiveCfg = Release|Any CPU {89D96078-2951-44C2-B5B1-1DA0D5E94C0C}.Release|x86.Build.0 = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|ARM.ActiveCfg = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|ARM.Build.0 = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|ARM64.Build.0 = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|x64.ActiveCfg = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|x64.Build.0 = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|x86.ActiveCfg = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Debug|x86.Build.0 = Debug|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|Any CPU.Build.0 = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|ARM.ActiveCfg = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|ARM.Build.0 = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|ARM64.ActiveCfg = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|ARM64.Build.0 = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|x64.ActiveCfg = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|x64.Build.0 = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|x86.ActiveCfg = Release|Any CPU + {99DFA130-C0F4-4D1F-8428-5CACA481B688}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -501,6 +523,7 @@ Global {8F87B73D-8A5D-4335-96E9-97EE0495B671} = {D6E372DC-10D3-4997-9DFC-568B4666635A} {A26EF3E9-C267-499C-B5AB-E4FB3C7AB6E1} = {D6E372DC-10D3-4997-9DFC-568B4666635A} {89D96078-2951-44C2-B5B1-1DA0D5E94C0C} = {D6E372DC-10D3-4997-9DFC-568B4666635A} + {99DFA130-C0F4-4D1F-8428-5CACA481B688} = {D6E372DC-10D3-4997-9DFC-568B4666635A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1765ABAA-0652-4DA5-ABBF-05396F2957D7} diff --git a/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/IWindowsExplorerHandler.cs b/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/IWindowsExplorerHandler.cs new file mode 100644 index 00000000..f01ed223 --- /dev/null +++ b/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/IWindowsExplorerHandler.cs @@ -0,0 +1,8 @@ +namespace RepoM.Plugin.WindowsExplorerGitInfo.PInvoke.Explorer; + +internal interface IWindowsExplorerHandler +{ + void UpdateTitles(); + + void CleanTitles(); +} \ No newline at end of file diff --git a/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/WindowsExplorerHandler.cs b/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/WindowsExplorerHandler.cs index 44bc01c2..5b5feeba 100644 --- a/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/WindowsExplorerHandler.cs +++ b/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/Explorer/WindowsExplorerHandler.cs @@ -1,9 +1,8 @@ namespace RepoM.Plugin.WindowsExplorerGitInfo.PInvoke.Explorer; -using JetBrains.Annotations; using RepoM.Api.Git; -internal class WindowsExplorerHandler +internal class WindowsExplorerHandler : IWindowsExplorerHandler { private readonly IRepositoryInformationAggregator _repositoryInfoAggregator; @@ -18,7 +17,6 @@ public void UpdateTitles() actor.Pulse(); } - [PublicAPI] public void CleanTitles() { var actor = new CleanWindowTitleActor(); diff --git a/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/WindowPath.cs b/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/WindowPath.cs index 85909056..d733d845 100644 --- a/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/WindowPath.cs +++ b/src/RepoM.Plugin.WindowsExplorerGitInfo/PInvoke/WindowPath.cs @@ -2,7 +2,7 @@ namespace RepoM.Plugin.WindowsExplorerGitInfo.PInvoke; using System; -public class WindowPath +internal class WindowPath { public WindowPath(IntPtr handle, string path) { diff --git a/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowExplorerBarGitInfoModule.cs b/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowExplorerBarGitInfoModule.cs index 404aab45..c8fc1de3 100644 --- a/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowExplorerBarGitInfoModule.cs +++ b/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowExplorerBarGitInfoModule.cs @@ -11,9 +11,9 @@ namespace RepoM.Plugin.WindowsExplorerGitInfo; internal class WindowExplorerBarGitInfoModule : IModule { private readonly Timer _explorerUpdateTimer; - private readonly WindowsExplorerHandler _explorerHandler; + private readonly IWindowsExplorerHandler _explorerHandler; - public WindowExplorerBarGitInfoModule(WindowsExplorerHandler explorerHandler) + public WindowExplorerBarGitInfoModule(IWindowsExplorerHandler explorerHandler) { _explorerHandler = explorerHandler ?? throw new ArgumentNullException(nameof(explorerHandler)); _explorerUpdateTimer = new Timer(RefreshTimerCallback, null, Timeout.Infinite, Timeout.Infinite); diff --git a/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowsExplorerGitInfoModule.cs b/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowsExplorerGitInfoPackage.cs similarity index 72% rename from src/RepoM.Plugin.WindowsExplorerGitInfo/WindowsExplorerGitInfoModule.cs rename to src/RepoM.Plugin.WindowsExplorerGitInfo/WindowsExplorerGitInfoPackage.cs index d46628bf..ac5cafd2 100644 --- a/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowsExplorerGitInfoModule.cs +++ b/src/RepoM.Plugin.WindowsExplorerGitInfo/WindowsExplorerGitInfoPackage.cs @@ -7,11 +7,11 @@ namespace RepoM.Plugin.WindowsExplorerGitInfo; using SimpleInjector.Packaging; [UsedImplicitly] -public class WindowsExplorerGitInfoModule : IPackage +public class WindowsExplorerGitInfoPackage : IPackage { public void RegisterServices(Container container) { - container.Register(Lifestyle.Singleton); + container.Register(Lifestyle.Singleton); container.Collection.Append(Lifestyle.Singleton); } } \ No newline at end of file diff --git a/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests.csproj b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests.csproj new file mode 100644 index 00000000..0f971316 --- /dev/null +++ b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests.csproj @@ -0,0 +1,38 @@ + + + + net6.0-windows + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/TestFramework/VerifierInitializer.cs b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/TestFramework/VerifierInitializer.cs new file mode 100644 index 00000000..b48df2c9 --- /dev/null +++ b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/TestFramework/VerifierInitializer.cs @@ -0,0 +1,15 @@ +namespace RepoM.Plugin.WindowsExplorerGitInfo.Tests.TestFramework; + +using System.Runtime.CompilerServices; +using Argon; +using VerifyTests; + +public static class VerifierInitializer +{ + [ModuleInitializer] + public static void Initialize() + { + VerifierSettings.DisableRequireUniquePrefix(); + VerifierSettings.AddExtraSettings(serializerSettings => serializerSettings.TypeNameHandling = TypeNameHandling.Auto); + } +} \ No newline at end of file diff --git a/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/WindowExplorerBarGitInfoModuleTest.cs b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/WindowExplorerBarGitInfoModuleTest.cs new file mode 100644 index 00000000..2129be18 --- /dev/null +++ b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/WindowExplorerBarGitInfoModuleTest.cs @@ -0,0 +1,105 @@ +namespace RepoM.Plugin.WindowsExplorerGitInfo.Tests; + +using System; +using System.Threading; +using System.Threading.Tasks; +using FakeItEasy; +using FluentAssertions; +using RepoM.Plugin.WindowsExplorerGitInfo.PInvoke.Explorer; +using Xunit; + +public class WindowExplorerBarGitInfoModuleTest +{ + private readonly IWindowsExplorerHandler _explorerHandler; + private readonly WindowExplorerBarGitInfoModule _sut; + + public WindowExplorerBarGitInfoModuleTest() + { + _explorerHandler = A.Fake(); + _sut = new WindowExplorerBarGitInfoModule(_explorerHandler); + } + + [Fact] + public void Ctor_ShouldThrow_WhenArgumentIsNull() + { + // arrange + + // act + Action act = () => _ = new WindowExplorerBarGitInfoModule(null!); + + // assert + act.Should().ThrowExactly(); + } + + [Fact] + public async Task StopAsync_ShouldCleanTitles() + { + // arrange + + // act + await _sut.StopAsync(); + + // assert + A.CallTo(() => _explorerHandler.CleanTitles()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task StartAsync_ShouldCallUpdateTitlesUntilStopped() + { + // arrange + var count = 0; + var mre = new ManualResetEvent(false); + A.CallTo(() => _explorerHandler.UpdateTitles()). + Invokes(_ => + { + var currentCount = Interlocked.Increment(ref count); + if (currentCount == 3) + { + mre.Set(); + } + }); + + // act + await _sut.StartAsync(); + _ = mre.WaitOne(TimeSpan.FromSeconds(5)); + + // assert + A.CallTo(() => _explorerHandler.UpdateTitles()).MustHaveHappened(3, Times.Exactly); + A.CallTo(() => _explorerHandler.CleanTitles()).MustNotHaveHappened(); + } + + [Fact] + public async Task StopAsync_ShouldCancelTimerExecution_WhenStarted() + { + // arrange + var count = 0; + var mreAfterStart = new ManualResetEvent(false); + var mreAfterStop = new ManualResetEvent(false); + A.CallTo(() => _explorerHandler.UpdateTitles()). + Invokes(_ => + { + var currentCount = Interlocked.Increment(ref count); + if (currentCount == 2) + { + mreAfterStart.Set(); + return; + } + + if (currentCount > 2) + { + mreAfterStop.Set(); + } + }); + + await _sut.StartAsync(); + _ = mreAfterStart.WaitOne(TimeSpan.FromSeconds(2)); + + // act + await _sut.StopAsync(); + _ = mreAfterStop.WaitOne(TimeSpan.FromSeconds(2)); + + // assert + A.CallTo(() => _explorerHandler.UpdateTitles()).MustHaveHappened(2, Times.Exactly); + A.CallTo(() => _explorerHandler.CleanTitles()).MustHaveHappenedOnceExactly(); + } +} \ No newline at end of file diff --git a/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/WindowsExplorerGitInfoPackageTests.cs b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/WindowsExplorerGitInfoPackageTests.cs new file mode 100644 index 00000000..47c79fc0 --- /dev/null +++ b/tests/RepoM.Plugin.WindowsExplorerGitInfo.Tests/WindowsExplorerGitInfoPackageTests.cs @@ -0,0 +1,48 @@ +namespace RepoM.Plugin.WindowsExplorerGitInfo.Tests; + +using System; +using System.IO.Abstractions; +using FakeItEasy; +using Microsoft.Extensions.Logging; +using RepoM.Api.Git; +using RepoM.Core.Plugin.Common; +using SimpleInjector; +using Xunit; + +public class WindowsExplorerGitInfoPackageTests +{ + [Fact] + public void RegisterServices_ShouldBeSuccessful_WhenExternalDependenciesAreRegistered() + { + // arrange + var container = new Container(); + RegisterExternals(container); + var sut = new WindowsExplorerGitInfoPackage(); + + // act + sut.RegisterServices(container); + + // assert + // implicit, Verify throws when container is not valid. + container.Verify(VerificationOption.VerifyAndDiagnose); + } + + [Fact] + public void RegisterServices_ShouldFail_WhenExternalDependenciesAreNotRegistered() + { + // arrange + var container = new Container(); + var sut = new WindowsExplorerGitInfoPackage(); + + // act + sut.RegisterServices(container); + + // assert + Assert.Throws(() => container.Verify(VerificationOption.VerifyAndDiagnose)); + } + + private static void RegisterExternals(Container container) + { + container.RegisterSingleton(A.Dummy); + } +} \ No newline at end of file