using System.ComponentModel; using System.Net.Http; using CursorLang.Models; using CursorLang.Services; using CursorLang.ViewModels; namespace CursorLang.Tests.Infrastructure; /// /// Parts every test needs but few tests care about. /// internal static class Fake { internal static UpdateViewModel Updates() => new(new FakeUpdateService(), new FakeLocalizationService(), new AppSettings(), new UpdateOptions()); } /// /// The keyboard layout, with the test in charge of it. /// internal sealed class FakeKeyboardLayoutService : IKeyboardLayoutService { internal int StartCalls { get; private set; } internal int StopCalls { get; private set; } internal int SwitchCalls { get; private set; } internal KeyboardLayout CurrentLayout { get; set; } = KeyboardLayout.FromLocaleId(0x0409); public KeyboardLayout Current => CurrentLayout; public event EventHandler? LayoutChanged; public void Start() => StartCalls++; public void Stop() => StopCalls++; public void SwitchToNext() => SwitchCalls++; internal void RaiseLayoutChanged(KeyboardLayout layout, LayoutChangeReason reason) => LayoutChanged?.Invoke(this, new LayoutChangedEventArgs(layout, reason)); internal bool HasSubscribers => LayoutChanged is not null; } /// /// A tooltip that pops up nowhere and merely remembers what it was asked for. /// internal sealed class FakeLayoutPopupService : ILayoutPopupService { internal List Shown { get; } = []; internal List ShownUntilHidden { get; } = []; internal int HideCalls { get; private set; } public void Show(KeyboardLayout layout) => Shown.Add(layout); public void ShowUntilHidden(KeyboardLayout layout) => ShownUntilHidden.Add(layout); public void Hide() => HideCalls++; } /// /// The Caps Lock interception without intercepting anything: the test supplies the presses. /// internal sealed class FakeCapsLockHotkeyService : ICapsLockHotkeyService { public event EventHandler? Tapped; public event EventHandler? HoldStarted; public event EventHandler? HoldEnded; public bool IsRunning { get; private set; } internal int StartCalls { get; private set; } internal int StopCalls { get; private set; } public void Start() { StartCalls++; IsRunning = true; } public void Stop() { StopCalls++; IsRunning = false; } internal void RaiseTapped() => Tapped?.Invoke(this, EventArgs.Empty); internal void RaiseHoldStarted() => HoldStarted?.Invoke(this, EventArgs.Empty); internal void RaiseHoldEnded() => HoldEnded?.Invoke(this, EventArgs.Empty); internal bool HasSubscribers => Tapped is not null || HoldStarted is not null || HoldEnded is not null; } /// /// Startup whose state the test assigns. /// internal sealed class FakeStartupService : IStartupService { internal StartupState State { get; set; } = StartupState.Disabled; internal StartupState? AnswerOnEnable { get; set; } internal List Requests { get; } = []; internal int GetStateCalls { get; private set; } public Task GetStateAsync() { GetStateCalls++; return Task.FromResult(State); } public Task SetEnabledAsync(bool enabled) { Requests.Add(enabled); State = enabled ? AnswerOnEnable ?? StartupState.Enabled : StartupState.Disabled; return Task.FromResult(State); } } /// /// Releases the test writes itself, with no repository behind them. /// internal sealed class FakeUpdateService : IUpdateService { internal ReleaseInfo? Release { get; set; } internal Exception? Failure { get; set; } internal TaskCompletionSource? DownloadGate { get; set; } internal string PackagePath { get; set; } = string.Empty; internal int CheckCalls { get; private set; } internal List Installed { get; } = []; public bool IsSupported { get; set; } = true; public Version CurrentVersion { get; set; } = new(1, 0, 0, 0); public Task CheckAsync(CancellationToken cancellationToken) { CheckCalls++; return Failure is null ? Task.FromResult(Release) : Task.FromException(Failure); } public async Task DownloadAsync( ReleaseInfo release, IProgress? progress, CancellationToken cancellationToken) { if (DownloadGate is not null) { await DownloadGate.Task.WaitAsync(cancellationToken); } if (Failure is not null) { throw Failure; } progress?.Report(0.5); return PackagePath; } public void Install(string packagePath) => Installed.Add(packagePath); } /// /// A release list the test fills in, with no repository behind it. /// internal sealed class FakeReleaseFeed : IReleaseFeed { internal ReleaseInfo? Release { get; set; } internal Exception? Failure { get; set; } internal List Authorized { get; } = []; public Task GetLatestAsync(CancellationToken cancellationToken) => Failure is null ? Task.FromResult(Release) : Task.FromException(Failure); public void Authorize(HttpRequestMessage request) => Authorized.Add(request); } /// /// Interface strings without resources: the key comes back as is, tagged with the language. /// internal sealed class FakeLocalizationService : ILocalizationService { private string _currentLanguage = "en"; public event PropertyChangedEventHandler? PropertyChanged; internal List RequestedKeys { get; } = []; public string this[string key] { get { RequestedKeys.Add(key); return $"{_currentLanguage}:{key}"; } } public IReadOnlyList AvailableLanguages { get; } = [ new("en", "English"), new("ru", "Русский"), ]; public string CurrentLanguage { get => _currentLanguage; set { if (string.IsNullOrWhiteSpace(value) || value == _currentLanguage) { return; } _currentLanguage = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentLanguage))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(System.Windows.Data.Binding.IndexerName)); } } } /// /// A theme that paints nothing and only remembers the windows attached to it. /// internal sealed class FakeThemeService : IThemeService { public AppTheme CurrentTheme { get; set; } = AppTheme.Light; internal List Registered { get; } = []; public void Register(System.Windows.Window window) => Registered.Add(window); } /// /// A tooltip window that shows nothing. /// internal sealed class FakeLayoutPopupWindow : ILayoutPopupWindow { internal int ShowCalls { get; private set; } internal int HideCalls { get; private set; } internal int CloseCalls { get; private set; } internal List Calls { get; } = []; public void ShowPopup() { ShowCalls++; Calls.Add("show"); } public void Hide() { HideCalls++; Calls.Add("hide"); } public void Close() { CloseCalls++; Calls.Add("close"); } }