TASK 20 step 1: global hotkeys F1-F9 (scenes, stream, mutes, TRAX)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// TASK 20 integration: global hotkeys reach their commands. A real MainWindow
|
||||
/// attaches the GlobalHotkeyManager (fake registrar — no OS-wide registration in
|
||||
/// tests); posting a genuine WM_HOTKEY through the window's own HwndSource hook
|
||||
/// must stage the mapped scene and flip the mic mute. Closing must revoke every
|
||||
/// registration made.
|
||||
/// </summary>
|
||||
[Collection("RealApp")]
|
||||
public sealed class GlobalHotkeyTests : IDisposable
|
||||
{
|
||||
[Fact]
|
||||
public void WmHotkey_StagesScene_And_TogglesMic_And_RevokesOnClose()
|
||||
{
|
||||
_app.Run(Run);
|
||||
}
|
||||
|
||||
private readonly RealAppHost _app;
|
||||
|
||||
public GlobalHotkeyTests(RealAppHost app) => _app = app;
|
||||
|
||||
private sealed class FakeRegistrar : IHotkeyRegistrar
|
||||
{
|
||||
public readonly List<HotkeyId> Registered = new();
|
||||
public readonly List<HotkeyId> Unregistered = new();
|
||||
|
||||
public bool Register(IntPtr hwnd, HotkeyId id, uint virtualKey)
|
||||
{
|
||||
Registered.Add(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Unregister(IntPtr hwnd, HotkeyId id) => Unregistered.Add(id);
|
||||
}
|
||||
|
||||
private void Run()
|
||||
{
|
||||
var tempDb = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"ytLlive-hotkeys-{Guid.NewGuid():N}.db");
|
||||
ViewModels.MainViewModel.LayoutPathOverride = tempDb;
|
||||
GlobalHotkeyManager.RegistrarOverride = () => _registrar;
|
||||
var window = default(MainWindow);
|
||||
try
|
||||
{
|
||||
using (var schema = new ytLive.Services.LayoutStore(tempDb)) { }
|
||||
Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
|
||||
|
||||
window = new MainWindow();
|
||||
var vm = (ViewModels.MainViewModel)window.DataContext;
|
||||
|
||||
new System.Windows.Interop.WindowInteropHelper(window).EnsureHandle();
|
||||
var hwnd = new System.Windows.Interop.WindowInteropHelper(window).Handle;
|
||||
|
||||
Assert.Equal(Enum.GetValues(typeof(HotkeyId)).Length, _registrar.Registered.Count);
|
||||
|
||||
SendMessage(hwnd, GlobalHotkeyManager.WM_HOTKEY, (IntPtr)HotkeyId.SceneBrb, IntPtr.Zero);
|
||||
Assert.NotNull(vm.StagedScene);
|
||||
Assert.Equal("BRB", vm.StagedScene!.Name);
|
||||
|
||||
SendMessage(hwnd, GlobalHotkeyManager.WM_HOTKEY, (IntPtr)HotkeyId.SceneLive, IntPtr.Zero);
|
||||
Assert.Equal("Live", vm.StagedScene!.Name);
|
||||
|
||||
var wasMuted = vm.MicMuted;
|
||||
SendMessage(hwnd, GlobalHotkeyManager.WM_HOTKEY, (IntPtr)HotkeyId.ToggleMicMute, IntPtr.Zero);
|
||||
Assert.NotEqual(wasMuted, vm.MicMuted);
|
||||
|
||||
window.Close();
|
||||
window = null;
|
||||
Assert.Equal(_registrar.Registered.Count, _registrar.Unregistered.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
window?.Close();
|
||||
GlobalHotkeyManager.RegistrarOverride = null;
|
||||
ViewModels.MainViewModel.LayoutPathOverride = null;
|
||||
Microsoft.Data.Sqlite.SqliteConnection.ClearAllPools();
|
||||
try { System.IO.File.Delete(tempDb); } catch { /* best-effort cleanup */ }
|
||||
}
|
||||
}
|
||||
|
||||
private readonly FakeRegistrar _registrar = new();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
public void Dispose() => GC.SuppressFinalize(this);
|
||||
}
|
||||
Reference in New Issue
Block a user