Files
cursor-lang/CursorLang.Core.Tests/Services/KeyboardLayoutServiceTests.cs
T
alex 55ac8e6556 lightweight variant (#1)
Reviewed-on: #1
Co-authored-by: Aleksandr Neychev <alexnejchev73@gmail.com>
2026-08-12 13:37:30 +00:00

287 lines
8.2 KiB
C#

using System.Collections.Concurrent;
using CursorLang.Core.Models;
using CursorLang.Core.Services;
using CursorLang.Tests.Shared;
namespace CursorLang.Core.Tests.Services;
/// <summary>
/// Watching the layout of the foreground window. The test supplies what the
/// system reports: what is under test is the decision about what counts as
/// a layout change.
/// </summary>
public sealed class KeyboardLayoutServiceTests
{
private static readonly IntPtr FirstWindow = new(1000);
private static readonly IntPtr SecondWindow = new(2000);
private const int English = 0x0409;
private const int Russian = 0x0419;
[Fact]
public void The_current_layout_is_taken_from_the_foreground_window()
{
using var world = new World { LocaleId = Russian };
KeyboardLayoutService service = world.CreateService();
Assert.Equal("RU", service.Current.ShortName);
world.LocaleId = English;
Assert.Equal("EN", service.Current.ShortName);
}
[Fact]
public void Switching_asks_the_system_to_change_the_layout()
{
using var world = new World();
KeyboardLayoutService service = world.CreateService();
service.SwitchToNext();
service.SwitchToNext();
Assert.Equal(2, world.SwitchRequests);
}
[Fact]
public void A_layout_change_in_the_same_window_counts_as_the_users_doing()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService();
Pump.Run(service.Start);
world.LocaleId = Russian;
Pump.Run(service.Poll);
LayoutChangedEventArgs change = Assert.Single(world.Changes);
Assert.Equal(LayoutChangeReason.UserSwitched, change.Reason);
Assert.Equal("RU", change.Layout.ShortName);
}
[Fact]
public void Moving_to_another_application_differs_from_switching()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService();
Pump.Run(service.Start);
world.ForegroundWindow = SecondWindow;
world.LocaleId = Russian;
Pump.Run(service.Poll);
LayoutChangedEventArgs change = Assert.Single(world.Changes);
Assert.Equal(LayoutChangeReason.ApplicationSwitched, change.Reason);
}
// Moving to an application with the same layout changes nothing
[Fact]
public void Moving_without_a_layout_change_yields_no_events()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService();
Pump.Run(service.Start);
world.ForegroundWindow = SecondWindow;
Pump.Run(service.Poll);
Assert.Empty(world.Changes);
}
[Fact]
public void An_unchanged_layout_yields_no_events()
{
using var world = new World { LocaleId = Russian };
KeyboardLayoutService service = world.CreateService();
Pump.Run(service.Start);
for (int i = 0; i < 5; i++)
{
Pump.Run(service.Poll);
}
Assert.Empty(world.Changes);
}
// There is no foreground window — during a desktop switch, for one
[Fact]
public void Without_a_foreground_window_the_poll_is_skipped()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService();
Pump.Run(service.Start);
world.ForegroundWindow = IntPtr.Zero;
world.LocaleId = Russian;
Pump.Run(service.Poll);
Assert.Empty(world.Changes);
// And the layout was not remembered: the change is noticed once a window is back
world.ForegroundWindow = FirstWindow;
Pump.Run(service.Poll);
Assert.Single(world.Changes);
}
[Fact]
public void One_change_yields_exactly_one_event()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService();
Pump.Run(service.Start);
world.LocaleId = Russian;
Pump.Run(service.Poll);
Pump.Run(service.Poll);
Assert.Single(world.Changes);
}
[Fact]
public void A_layout_change_before_the_watch_starts_goes_unnoticed()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService();
world.LocaleId = Russian;
Pump.Run(service.Start);
Pump.Run(service.Poll);
// Start remembered the layout that was in place at that moment
Assert.Empty(world.Changes);
}
[Fact]
public void The_watch_runs_on_a_timer()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService(TimeSpan.FromMilliseconds(15));
Pump.Run(service.Start);
world.LocaleId = Russian;
Pump.WaitFor(() => !world.Changes.IsEmpty, "the timer noticed the layout change");
}
[Fact]
public void Stopping_ends_the_polling()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService(TimeSpan.FromMilliseconds(15));
Pump.Run(service.Start);
Pump.Run(service.Stop);
world.LocaleId = Russian;
Pump.Pause(TimeSpan.FromMilliseconds(120));
Assert.Empty(world.Changes);
}
[Fact]
public void Closing_ends_the_polling()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService(TimeSpan.FromMilliseconds(15));
Pump.Run(service.Start);
Pump.Run(service.Dispose);
world.LocaleId = Russian;
Pump.Pause(TimeSpan.FromMilliseconds(120));
Assert.Empty(world.Changes);
}
[Fact]
public void The_watch_can_be_resumed()
{
using var world = new World { LocaleId = English };
KeyboardLayoutService service = world.CreateService(TimeSpan.FromMilliseconds(15));
Pump.Run(service.Start);
Pump.Run(service.Stop);
Pump.Run(service.Start);
world.LocaleId = Russian;
Pump.WaitFor(() => !world.Changes.IsEmpty, "the watch resumed");
}
// The ordinary service asks Windows itself about the layout
[Fact]
public void The_service_can_work_with_the_real_system()
{
KeyboardLayoutService service = Pump.Run(() =>
new KeyboardLayoutService(new KeyboardLayoutOptions { PollInterval = TimeSpan.FromHours(1) }));
try
{
Pump.Run(service.Start);
Pump.Run(service.Poll);
Assert.InRange(service.Current.LocaleId, 1, 0xFFFF);
Pump.Run(service.Stop);
}
finally
{
Pump.Run(service.Dispose);
}
}
[Fact]
public void By_default_the_poll_runs_more_than_six_times_a_second()
{
// Any rarer and the tooltip would visibly lag behind the keystroke
Assert.True(new KeyboardLayoutOptions().PollInterval <= TimeSpan.FromMilliseconds(150));
}
/// <summary>
/// The state of the system as the service sees it, and everything the
/// service reported about it.
/// </summary>
private sealed class World : IDisposable
{
private KeyboardLayoutService? _service;
internal IntPtr ForegroundWindow { get; set; } = FirstWindow;
internal int LocaleId { get; set; } = English;
internal int SwitchRequests { get; private set; }
internal ConcurrentQueue<LayoutChangedEventArgs> Changes { get; } = new();
internal KeyboardLayoutService CreateService(TimeSpan? pollInterval = null)
{
var options = new KeyboardLayoutOptions
{
PollInterval = pollInterval ?? TimeSpan.FromHours(1),
};
_service = Pump.Run(() => new KeyboardLayoutService(
options,
() => ForegroundWindow,
() => LocaleId,
() => SwitchRequests++));
_service.LayoutChanged += (_, e) => Changes.Enqueue(e);
return _service;
}
public void Dispose()
{
if (_service is not null)
{
Pump.Run(_service.Dispose);
}
}
}
}