using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Xunit; using ytLive.Services; namespace ytLive.Tests; /// /// 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. /// [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 Registered = new(); public readonly List Unregistered = new(); public bool Register(IntPtr hwnd, HotkeyId id, uint modifiers, 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); }