182 lines
5.8 KiB
C#
182 lines
5.8 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Interop;
|
|
using CursorLang.Interop;
|
|
using CursorLang.Models;
|
|
using CursorLang.Tests.Infrastructure;
|
|
using CursorLang.ViewModels;
|
|
using CursorLang.Views;
|
|
|
|
namespace CursorLang.Tests.Interop;
|
|
|
|
/// <summary>
|
|
/// Checks that need a real foreground window with an input field: the caret
|
|
/// and the layout switch live exactly there.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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");
|
|
}
|
|
|
|
var settings = new AppSettings
|
|
{
|
|
PlacementMode = PopupPlacementMode.AtCaret,
|
|
CaretSide = AnchorSide.BottomRight,
|
|
CaretOffset = 8,
|
|
};
|
|
|
|
var viewModel = new LayoutPopupViewModel(settings) { ShortName = "RU" };
|
|
var popup = new LayoutPopupWindow(viewModel, settings);
|
|
|
|
try
|
|
{
|
|
popup.ShowPopup();
|
|
|
|
PopupWindowNative.Rect bounds =
|
|
WindowPlacementNative.TryGetBounds(new WindowInteropHelper(popup).Handle)!.Value;
|
|
|
|
// The tooltip landed to the right of and below the caret — as asked
|
|
Assert.True(bounds.Left >= caret.Value.Right);
|
|
Assert.True(bounds.Top >= caret.Value.Bottom);
|
|
}
|
|
finally
|
|
{
|
|
popup.Close();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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));
|
|
}
|
|
});
|
|
}
|
|
|
|
/// <summary>A window with an input field brought to the foreground.</summary>
|
|
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; }
|
|
|
|
/// <summary>Skips the check if the window never became the foreground one.</summary>
|
|
internal void RequireForeground()
|
|
{
|
|
if (KeyboardLayoutNative.GetForegroundWindow() != Handle)
|
|
{
|
|
Assert.Skip("The window could not be brought to the foreground");
|
|
}
|
|
}
|
|
|
|
public void Dispose() => _window.Close();
|
|
}
|
|
}
|