using System.Windows; using System.Windows.Controls; using System.Windows.Interop; using CursorLang.Core.Interop; using CursorLang.Settings.Interop; using CursorLang.Settings.Tests.Infrastructure; namespace CursorLang.Settings.Tests.Interop; /// /// Checks that need a real foreground window with an input field: the caret /// and the layout switch live exactly there. /// /// /// Windows does not always allow a window to come forward — when the screen is /// locked, say, or when the run happens in a session without a desktop. In /// those cases the check reports itself as skipped rather than failed: there /// would be nothing to verify. /// public sealed class ForegroundWindowTests { [Fact] public void The_caret_in_an_input_field_is_found() { Sta.Run(() => { using var input = new InputWindow(); input.RequireForeground(); PopupWindowNative.Rect? caret = CaretNative.TryGetCaretRect(); if (caret is null) { Assert.Skip("The input field did not report the caret position"); } // The caret has to sit inside the input window and to have a height PopupWindowNative.Rect bounds = WindowPlacementNative.TryGetBounds(input.Handle)!.Value; Assert.True(caret.Value.Bottom > caret.Value.Top); Assert.InRange(caret.Value.Left, bounds.Left, bounds.Right); Assert.InRange(caret.Value.Top, bounds.Top, bounds.Bottom); }); } [Fact] public void The_tooltip_at_the_caret_lands_next_to_it() { Sta.Run(() => { using var input = new InputWindow(); input.RequireForeground(); PopupWindowNative.Rect? caret = CaretNative.TryGetCaretRect(); if (caret is null) { Assert.Skip("The input field did not report the caret position"); } PopupWindowNative.Rect field = WindowPlacementNative.TryGetBounds(input.Handle)!.Value; Assert.True(CaretNative.IsInside(caret.Value, field), "the caret reported by the input field is outside that field"); }); } // The request to switch the layout goes to the window holding the input // focus, so it only concerns the test window itself [Fact] public void The_request_to_change_the_layout_reaches_its_own_window() { Sta.Run(() => { using var input = new InputWindow(); input.RequireForeground(); int before = KeyboardLayoutNative.GetActiveLocaleId(); KeyboardLayoutNative.RequestNextLayout(); Sta.Pause(TimeSpan.FromMilliseconds(150)); int after = KeyboardLayoutNative.GetActiveLocaleId(); Assert.InRange(after, 1, 0xFFFF); if (after == before) { // The system may hold a single layout — there is nothing to switch to return; } // Bring the layout back around the circle to where it was for (int i = 0; i < 8 && KeyboardLayoutNative.GetActiveLocaleId() != before; i++) { KeyboardLayoutNative.RequestNextLayout(); Sta.Pause(TimeSpan.FromMilliseconds(150)); } }); } /// A window with an input field brought to the foreground. private sealed class InputWindow : IDisposable { private readonly Window _window; internal InputWindow() { var box = new TextBox { Text = "check", FontSize = 20 }; _window = new Window { Width = 400, Height = 200, ShowInTaskbar = false, WindowStartupLocation = WindowStartupLocation.Manual, Left = 100, Top = 100, Topmost = true, Content = box, }; _window.Show(); Handle = new WindowInteropHelper(_window).Handle; // Windows grants the right to bring a window forward neither to // everyone nor at once, so it takes a few attempts for (int attempt = 0; attempt < 10; attempt++) { _window.Activate(); box.Focus(); box.CaretIndex = box.Text.Length; Sta.Pause(TimeSpan.FromMilliseconds(50)); if (KeyboardLayoutNative.GetForegroundWindow() == Handle) { break; } } // The caret does not appear at the same instant as the focus Sta.Pause(TimeSpan.FromMilliseconds(100)); } internal IntPtr Handle { get; } /// Skips the check if the window never became the foreground one. internal void RequireForeground() { if (KeyboardLayoutNative.GetForegroundWindow() != Handle) { Assert.Skip("The window could not be brought to the foreground"); } } public void Dispose() => _window.Close(); } }