- 新增 HotKeyManager (Helpers/), 使用 user32.dll RegisterHotKey/UnregisterHotKey 注册系统级快捷键 - 支持 Ctrl/Alt/Shift/Win 修饰键组合, 单字符键和命名键(F1-F12等)解析 - MainWindow.OnSourceInitialized 挂载 HwndSource.AddHook 拦截 WM_HOTKEY 消息 - 启动时自动注册所有含快捷键的工具, 工具增删改后自动重新注册 - 快捷键冲突时记录 Win32 错误码, 无效格式打印警告 - 新增 12 个 HotKeyManager 单元测试 (73 tests total)
136 lines
3.7 KiB
C#
136 lines
3.7 KiB
C#
using Moq;
|
|
using PersonalToolBox.Helpers;
|
|
using PersonalToolBox.Models;
|
|
using PersonalToolBox.Services;
|
|
|
|
namespace PersonalToolBox.Tests.Helpers;
|
|
|
|
public class HotKeyManagerTests
|
|
{
|
|
[Fact]
|
|
public void TryParseHotKey_ValidCtrlAltT_ReturnsCorrectModifiersAndVk()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("Ctrl+Alt+T", out uint mod, out uint vk);
|
|
|
|
Assert.True(result);
|
|
Assert.NotEqual(0u, mod);
|
|
// 'T' → VK = 0x54
|
|
Assert.Equal((uint)'T', vk);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_ValidCtrlShiftF1_ReturnsCorrectVk()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("Ctrl+Shift+F1", out uint mod, out uint vk);
|
|
|
|
Assert.True(result);
|
|
Assert.NotEqual(0u, mod);
|
|
// F1 → VK_F1 = 0x70
|
|
Assert.Equal(0x70u, vk);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_OnlyModifier_ReturnsFalse()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("Ctrl", out _, out _);
|
|
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_OnlyKey_ReturnsFalse()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("T", out _, out _);
|
|
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_Empty_ReturnsFalse()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("", out _, out _);
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_Null_ReturnsFalse()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey(null!, out _, out _);
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_Whitespace_ReturnsFalse()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey(" ", out _, out _);
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_WinKey_B_ReturnsCorrectVk()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("Win+B", out uint mod, out uint vk);
|
|
|
|
Assert.True(result);
|
|
Assert.Equal((uint)'B', vk);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseHotKey_UnknownModifier_ReturnsFalse()
|
|
{
|
|
var result = HotKeyManager.TryParseHotKey("Foo+T", out _, out _);
|
|
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public void RegisterAll_SkipsEmptyHotKeys()
|
|
{
|
|
var logMock = new Mock<ILogService>();
|
|
var processMock = new Mock<IProcessExecutionService>();
|
|
var manager = new HotKeyManager(logMock.Object, processMock.Object);
|
|
|
|
var tools = new[]
|
|
{
|
|
new ToolItem { Name = "NoHotkey", HotKey = "" },
|
|
new ToolItem { Name = "BlankHotkey", HotKey = " " }
|
|
};
|
|
|
|
// Should not throw
|
|
manager.RegisterAll(tools);
|
|
}
|
|
|
|
[Fact]
|
|
public void RegisterAll_InvalidFormat_LogsWarning()
|
|
{
|
|
var logMock = new Mock<ILogService>();
|
|
var processMock = new Mock<IProcessExecutionService>();
|
|
var manager = new HotKeyManager(logMock.Object, processMock.Object);
|
|
|
|
// Initialize needs a hwnd, but RegisterAll calls Register which checks _hwnd
|
|
// So calling RegisterAll without Initialize should be a no-op
|
|
var tools = new[]
|
|
{
|
|
new ToolItem { Name = "Bad", HotKey = "not-a-valid-key" }
|
|
};
|
|
|
|
// hwnd is IntPtr.Zero, so should skip silently
|
|
manager.RegisterAll(tools);
|
|
|
|
// hwnd is zero, so Register is skipped entirely
|
|
logMock.Verify(x => x.Warning(It.IsAny<string>()), Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryHandleMessage_NonWMHOTKEY_ReturnsFalse()
|
|
{
|
|
var logMock = new Mock<ILogService>();
|
|
var processMock = new Mock<IProcessExecutionService>();
|
|
var manager = new HotKeyManager(logMock.Object, processMock.Object);
|
|
|
|
var handled = manager.TryHandleMessage(9999, IntPtr.Zero, IntPtr.Zero);
|
|
|
|
Assert.False(handled);
|
|
}
|
|
}
|