diff --git a/src/DiffEngine.Tests/DiffToolsTest.cs b/src/DiffEngine.Tests/DiffToolsTest.cs index da3589f4..422a58b1 100644 --- a/src/DiffEngine.Tests/DiffToolsTest.cs +++ b/src/DiffEngine.Tests/DiffToolsTest.cs @@ -217,6 +217,77 @@ public void TryFindByName() } #endif **/ + [Fact] + public void TryFindByName_IsCaseInsensitive() + { + var diffToolPath = FakeDiffTool.Exe; + + DiffTools.AddTool( + name: "MyCaseSensitiveTool", + autoRefresh: true, + isMdi: false, + supportsText: true, + requiresTarget: true, + useShellExecute: true, + launchArguments: new( + Left: (tempFile, targetFile) => $"\"{targetFile}\" \"{tempFile}\"", + Right: (tempFile, targetFile) => $"\"{tempFile}\" \"{targetFile}\""), + exePath: diffToolPath, + binaryExtensions: []); + + // Exact case + Assert.True(DiffTools.TryFindByName("MyCaseSensitiveTool", out var tool1)); + Assert.Equal("MyCaseSensitiveTool", tool1!.Name); + + // All lowercase + Assert.True(DiffTools.TryFindByName("mycasesensitivetool", out var tool2)); + Assert.Equal("MyCaseSensitiveTool", tool2!.Name); + + // All uppercase + Assert.True(DiffTools.TryFindByName("MYCASESENSITIVETOOL", out var tool3)); + Assert.Equal("MyCaseSensitiveTool", tool3!.Name); + + // Mixed case + Assert.True(DiffTools.TryFindByName("myCASEsensitiveTOOL", out var tool4)); + Assert.Equal("MyCaseSensitiveTool", tool4!.Name); + } + + [Fact] + public void AddTool_RejectsDuplicateNameCaseInsensitive() + { + var diffToolPath = FakeDiffTool.Exe; + + DiffTools.AddTool( + name: "UniqueTool", + autoRefresh: true, + isMdi: false, + supportsText: true, + requiresTarget: true, + useShellExecute: true, + launchArguments: new( + Left: (tempFile, targetFile) => $"\"{targetFile}\" \"{tempFile}\"", + Right: (tempFile, targetFile) => $"\"{tempFile}\" \"{targetFile}\""), + exePath: diffToolPath, + binaryExtensions: []); + + // Adding with different case should throw + var exception = Assert.Throws(() => + DiffTools.AddTool( + name: "UNIQUETOOL", + autoRefresh: true, + isMdi: false, + supportsText: true, + requiresTarget: true, + useShellExecute: true, + launchArguments: new( + Left: (tempFile, targetFile) => $"\"{targetFile}\" \"{tempFile}\"", + Right: (tempFile, targetFile) => $"\"{tempFile}\" \"{targetFile}\""), + exePath: diffToolPath, + binaryExtensions: [])); + + Assert.Contains("Tool with name already exists", exception.Message); + } + public DiffToolsTest(ITestOutputHelper output) : base(output) => DiffTools.Reset(); diff --git a/src/DiffEngine.Tests/TargetPositionTest.cs b/src/DiffEngine.Tests/TargetPositionTest.cs new file mode 100644 index 00000000..b2e14556 --- /dev/null +++ b/src/DiffEngine.Tests/TargetPositionTest.cs @@ -0,0 +1,44 @@ +public class TargetPositionTest : + XunitContextBase +{ + [Theory] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("True", true)] + [InlineData("tRuE", true)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData("False", false)] + [InlineData("fAlSe", false)] + public void ParseTargetOnLeft_IsCaseInsensitive(string input, bool expected) + { + var result = TargetPosition.ParseTargetOnLeft(input); + Assert.Equal(expected, result); + } + + [Fact] + public void ParseTargetOnLeft_NullReturnsNull() + { + var result = TargetPosition.ParseTargetOnLeft(null); + Assert.Null(result); + } + + [Theory] + [InlineData("yes")] + [InlineData("no")] + [InlineData("1")] + [InlineData("0")] + [InlineData("")] + [InlineData("invalid")] + public void ParseTargetOnLeft_InvalidValueThrows(string input) + { + var exception = Assert.Throws(() => TargetPosition.ParseTargetOnLeft(input)); + Assert.Contains("Unable to parse Position", exception.Message); + Assert.Contains(input, exception.Message); + } + + public TargetPositionTest(ITestOutputHelper output) : + base(output) + { + } +} diff --git a/src/DiffEngine/DiffEngine.csproj b/src/DiffEngine/DiffEngine.csproj index 2a93061b..3eab5cd0 100644 --- a/src/DiffEngine/DiffEngine.csproj +++ b/src/DiffEngine/DiffEngine.csproj @@ -16,9 +16,7 @@ - - diff --git a/src/DiffEngine/DiffTools_Add.cs b/src/DiffEngine/DiffTools_Add.cs index 2b7f2474..35223f1c 100644 --- a/src/DiffEngine/DiffTools_Add.cs +++ b/src/DiffEngine/DiffTools_Add.cs @@ -52,7 +52,7 @@ public static partial class DiffTools static ResolvedTool? AddInner(string name, DiffTool? diffTool, bool autoRefresh, bool isMdi, bool supportsText, bool requiresTarget, IEnumerable binaries, string exePath, LaunchArguments launchArguments, bool useShellExecute, bool createNoWindow) { Guard.AgainstEmpty(name, nameof(name)); - if (resolved.Any(_ => _.Name == name)) + if (resolved.Any(_ => _.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException($"Tool with name already exists. Name: {name}", nameof(name)); } diff --git a/src/DiffEngine/DiffTools_TryFind.cs b/src/DiffEngine/DiffTools_TryFind.cs index 0fd01007..6543d5c2 100644 --- a/src/DiffEngine/DiffTools_TryFind.cs +++ b/src/DiffEngine/DiffTools_TryFind.cs @@ -65,7 +65,7 @@ public static bool TryFindByName( string name, [NotNullWhen(true)] out ResolvedTool? resolvedTool) { - resolvedTool = resolved.SingleOrDefault(_ => _.Name.Equals(name, StringComparison.Ordinal)); + resolvedTool = resolved.SingleOrDefault(_ => _.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); return resolvedTool != null; } } \ No newline at end of file diff --git a/src/DiffEngine/FodyWeavers.xml b/src/DiffEngine/FodyWeavers.xml deleted file mode 100644 index 8a79cf5e..00000000 --- a/src/DiffEngine/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/DiffEngine/OsSettingsResolver.cs b/src/DiffEngine/OsSettingsResolver.cs index 6a99eb24..b1585ed1 100644 --- a/src/DiffEngine/OsSettingsResolver.cs +++ b/src/DiffEngine/OsSettingsResolver.cs @@ -83,7 +83,7 @@ public static bool TryFindForEnvironmentVariable(string tool, string exeName, [N return false; } - if (basePath.EndsWith(exeName) && + if (basePath.EndsWith(exeName, StringComparison.OrdinalIgnoreCase) && File.Exists(basePath)) { envPath = basePath; diff --git a/src/DiffEngine/TargetPosition.cs b/src/DiffEngine/TargetPosition.cs index 67b086d6..6bc1cbe9 100644 --- a/src/DiffEngine/TargetPosition.cs +++ b/src/DiffEngine/TargetPosition.cs @@ -5,21 +5,22 @@ static TargetPosition() => TargetOnLeft = ReadTargetOnLeft().GetValueOrDefault(false); - static bool? ReadTargetOnLeft() - { - var value = Environment.GetEnvironmentVariable("DiffEngine_TargetOnLeft"); + static bool? ReadTargetOnLeft() => + ParseTargetOnLeft(Environment.GetEnvironmentVariable("DiffEngine_TargetOnLeft")); + internal static bool? ParseTargetOnLeft(string? value) + { if (value == null) { return null; } - if (value == "true") + if (string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) { return true; } - if (value == "false") + if (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/src/DiffEngineTray.Tests/ImagesTest.cs b/src/DiffEngineTray.Tests/ImagesTest.cs new file mode 100644 index 00000000..5f5eec39 --- /dev/null +++ b/src/DiffEngineTray.Tests/ImagesTest.cs @@ -0,0 +1,59 @@ +public class ImagesTest : + XunitContextBase +{ + [Fact] + public void AllImagesLoaded() + { + // Verify all icon resources load correctly + Assert.NotNull(Images.Active); + Assert.NotNull(Images.Default); + + // Verify all image resources load correctly + Assert.NotNull(Images.Exit); + Assert.NotNull(Images.Delete); + Assert.NotNull(Images.AcceptAll); + Assert.NotNull(Images.Accept); + Assert.NotNull(Images.Discard); + Assert.NotNull(Images.VisualStudio); + Assert.NotNull(Images.Folder); + Assert.NotNull(Images.Options); + Assert.NotNull(Images.Link); + } + + [Fact] + public void IconsHaveValidSize() + { + Assert.True(Images.Active.Width > 0); + Assert.True(Images.Active.Height > 0); + Assert.True(Images.Default.Width > 0); + Assert.True(Images.Default.Height > 0); + } + + [Fact] + public void ImagesHaveValidSize() + { + Assert.True(Images.Exit.Width > 0); + Assert.True(Images.Exit.Height > 0); + Assert.True(Images.Delete.Width > 0); + Assert.True(Images.Delete.Height > 0); + Assert.True(Images.AcceptAll.Width > 0); + Assert.True(Images.AcceptAll.Height > 0); + Assert.True(Images.Accept.Width > 0); + Assert.True(Images.Accept.Height > 0); + Assert.True(Images.Discard.Width > 0); + Assert.True(Images.Discard.Height > 0); + Assert.True(Images.VisualStudio.Width > 0); + Assert.True(Images.VisualStudio.Height > 0); + Assert.True(Images.Folder.Width > 0); + Assert.True(Images.Folder.Height > 0); + Assert.True(Images.Options.Width > 0); + Assert.True(Images.Options.Height > 0); + Assert.True(Images.Link.Width > 0); + Assert.True(Images.Link.Height > 0); + } + + public ImagesTest(ITestOutputHelper output) : + base(output) + { + } +} diff --git a/src/DiffEngineTray.Tests/TrackerClearTest.cs b/src/DiffEngineTray.Tests/TrackerClearTest.cs index 3d03166d..7d567ccf 100644 --- a/src/DiffEngineTray.Tests/TrackerClearTest.cs +++ b/src/DiffEngineTray.Tests/TrackerClearTest.cs @@ -1,5 +1,5 @@ -public class TrackerClearTest : - XunitContextBase +public class TrackerClearTest(ITestOutputHelper output) : + XunitContextBase(output) { [Fact] public async Task Simple() @@ -11,13 +11,6 @@ public async Task Simple() tracker.AssertEmpty(); } - public TrackerClearTest(ITestOutputHelper output) : - base(output) - { - file1 = Path.GetTempFileName(); - file2 = Path.GetTempFileName(); - } - public override void Dispose() { File.Delete(file1); @@ -25,6 +18,6 @@ public override void Dispose() base.Dispose(); } - string file1; - string file2; -} \ No newline at end of file + string file1 = Path.GetTempFileName(); + string file2 = Path.GetTempFileName(); +} diff --git a/src/DiffEngineTray/DiffEngineTray.csproj b/src/DiffEngineTray/DiffEngineTray.csproj index 8ade3506..2fda4020 100644 --- a/src/DiffEngineTray/DiffEngineTray.csproj +++ b/src/DiffEngineTray/DiffEngineTray.csproj @@ -28,8 +28,6 @@ - - diff --git a/src/DiffEngineTray/FodyWeavers.xml b/src/DiffEngineTray/FodyWeavers.xml deleted file mode 100644 index 903f9ccf..00000000 --- a/src/DiffEngineTray/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/DiffEngineTray/GlobalUsings.cs b/src/DiffEngineTray/GlobalUsings.cs index dbd144a6..020b0f44 100644 --- a/src/DiffEngineTray/GlobalUsings.cs +++ b/src/DiffEngineTray/GlobalUsings.cs @@ -3,4 +3,5 @@ global using Serilog; global using System.Text.Json; global using System.Text.Json.Serialization; -global using System.Diagnostics.CodeAnalysis; \ No newline at end of file +global using System.Diagnostics.CodeAnalysis; +global using System.Reflection; \ No newline at end of file diff --git a/src/DiffEngineTray/Images/Images.cs b/src/DiffEngineTray/Images/Images.cs index e6dae341..59b19cee 100644 --- a/src/DiffEngineTray/Images/Images.cs +++ b/src/DiffEngineTray/Images/Images.cs @@ -1,30 +1,31 @@ -using Resourcer; - public static class Images { + static Stream GetStream(string name) => + Assembly.GetExecutingAssembly().GetManifestResourceStream($"DiffEngineTray.Images.{name}")!; + static Images() { - using var activeStream = Resource.AsStream("active.ico"); + using var activeStream = GetStream("active.ico"); Active = new(activeStream); - using var defaultStream = Resource.AsStream("default.ico"); + using var defaultStream = GetStream("default.ico"); Default = new(defaultStream); - using var exitStream = Resource.AsStream("exit.png"); + using var exitStream = GetStream("exit.png"); Exit = Image.FromStream(exitStream); - using var deleteStream = Resource.AsStream("delete.png"); + using var deleteStream = GetStream("delete.png"); Delete = Image.FromStream(deleteStream); - using var acceptAllStream = Resource.AsStream("acceptAll.png"); + using var acceptAllStream = GetStream("acceptAll.png"); AcceptAll = Image.FromStream(acceptAllStream); - using var acceptStream = Resource.AsStream("accept.png"); + using var acceptStream = GetStream("accept.png"); Accept = Image.FromStream(acceptStream); - using var discardStream = Resource.AsStream("discard.png"); + using var discardStream = GetStream("discard.png"); Discard = Image.FromStream(discardStream); - using var vsStream = Resource.AsStream("vs.png"); + using var vsStream = GetStream("vs.png"); VisualStudio = Image.FromStream(vsStream); - using var folderStream = Resource.AsStream("folder.png"); + using var folderStream = GetStream("folder.png"); Folder = Image.FromStream(folderStream); - using var optionsStream = Resource.AsStream("cogs.png"); + using var optionsStream = GetStream("cogs.png"); Options = Image.FromStream(optionsStream); - using var linkStream = Resource.AsStream("link.png"); + using var linkStream = GetStream("link.png"); Link = Image.FromStream(linkStream); } @@ -39,4 +40,4 @@ static Images() public static Image Options { get; } public static Icon Active { get; } public static Icon Default { get; } -} \ No newline at end of file +} diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index ef2f5636..72284eae 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,15 +5,12 @@ - - -