using CursorLang.Core.Models; using CursorLang.Core.Services; using CursorLang.Settings.ViewModels; using CursorLang.Tests.Shared; namespace CursorLang.Settings.Tests.ViewModels; /// /// The settings window in terms of what it shows and what it is in charge of. /// public sealed class SettingsViewModelTests { [Fact] public void The_interface_language_comes_from_the_settings() { var settings = new AppSettings { Language = "ru" }; var localization = new FakeLocalizationService(); using var viewModel = Create(settings, localization); Assert.Equal("ru", localization.CurrentLanguage); Assert.Same(settings, viewModel.Settings); Assert.Same(localization, viewModel.Localization); } [Fact] public void Changing_the_language_setting_switches_the_interface() { var settings = new AppSettings { Language = "en" }; var localization = new FakeLocalizationService(); using var viewModel = Create(settings, localization); settings.Language = "ru"; Assert.Equal("ru", localization.CurrentLanguage); } [Fact] public void Other_settings_leave_the_language_alone() { var settings = new AppSettings { Language = "en" }; var localization = new FakeLocalizationService(); using var viewModel = Create(settings, localization); settings.Current.FontSize = 30; Assert.Equal("en", localization.CurrentLanguage); } [Fact] public void The_lists_are_built_from_every_value_of_the_enums() { using SettingsViewModel viewModel = Create(); Assert.Equal(Enum.GetValues(), viewModel.Themes.Select(option => option.Value)); Assert.Equal(Enum.GetValues(), viewModel.PlacementModes.Select(option => option.Value)); Assert.Equal(Enum.GetValues(), viewModel.AnchorSides.Select(option => option.Value)); Assert.Equal(Enum.GetValues(), viewModel.ScreenPositions.Select(option => option.Value)); } // Next to the caret the popup only goes beside it: above or below is where the next // line of the text is [Fact] public void The_caret_is_offered_two_sides_and_no_more() { using SettingsViewModel viewModel = Create(); Assert.Equal( [CaretSide.Left, CaretSide.Right], viewModel.CaretSides.Select(option => option.Value)); } // A caption key is built from the type name and the value [Fact] public void The_option_captions_come_from_the_resources() { var localization = new FakeLocalizationService(); using var viewModel = Create(localization: localization); Assert.Equal("en:AppTheme_System", viewModel.Themes[0].Display); Assert.Contains("PopupPlacementMode_AtCursor", localization.RequestedKeys); } // If the list items were recreated, the ComboBox would drop the selected value [Fact] public void Changing_the_language_changes_the_captions_not_the_options() { var settings = new AppSettings { Language = "en" }; var localization = new FakeLocalizationService(); using var viewModel = Create(settings, localization); EnumOption first = viewModel.Themes[0]; settings.Language = "ru"; Assert.Same(first, viewModel.Themes[0]); Assert.Equal("ru:AppTheme_System", first.Display); Assert.Equal("ru:AnchorSide_TopLeft", viewModel.AnchorSides[0].Display); Assert.Equal("ru:CaretSide_Left", viewModel.CaretSides[0].Display); Assert.Equal("ru:ScreenPosition_TopLeft", viewModel.ScreenPositions[0].Display); Assert.Equal("ru:PopupPlacementMode_AtCursor", viewModel.PlacementModes[0].Display); } [Fact] public void The_background_and_text_palettes_are_non_empty_and_different() { using SettingsViewModel viewModel = Create(); Assert.NotEmpty(viewModel.BackgroundPalette); Assert.NotEmpty(viewModel.TextPalette); Assert.NotEqual(viewModel.BackgroundPalette, viewModel.TextPalette); } [Fact] public void The_palettes_hold_no_duplicates() { using SettingsViewModel viewModel = Create(); Assert.Equal(viewModel.BackgroundPalette.Count, viewModel.BackgroundPalette.Distinct().Count()); Assert.Equal(viewModel.TextPalette.Count, viewModel.TextPalette.Distinct().Count()); } [Fact] public void The_default_colours_are_present_in_the_palettes() { var settings = new AppSettings(); using var viewModel = Create(settings); Assert.Contains(settings.Current.BackgroundColor, viewModel.BackgroundPalette); Assert.Contains(settings.Current.ForegroundColor, viewModel.TextPalette); } [Fact] public void The_palettes_are_the_same_for_every_window() { using SettingsViewModel first = Create(); using SettingsViewModel second = Create(); Assert.Same(first.BackgroundPalette, second.BackgroundPalette); Assert.Same(first.TextPalette, second.TextPalette); } [Fact] public void The_background_palette_consists_of_colours() { using SettingsViewModel viewModel = Create(); Assert.All(viewModel.BackgroundPalette, color => Assert.IsType(color)); } [Fact] public void Until_Windows_answers_the_startup_setting_stays_hidden() { using SettingsViewModel viewModel = Create(); Assert.False(viewModel.IsStartupAvailable); Assert.False(viewModel.CanChangeStartup); Assert.False(viewModel.IsStartupLocked); Assert.False(viewModel.RunAtStartup); } [Theory] [InlineData(StartupState.Enabled, true, true, false, true)] [InlineData(StartupState.Disabled, true, true, false, false)] [InlineData(StartupState.DisabledByUser, true, false, true, false)] [InlineData(StartupState.DisabledByPolicy, true, false, true, false)] [InlineData(StartupState.EnabledByPolicy, true, false, true, true)] [InlineData(StartupState.Unavailable, false, false, false, false)] public async Task The_startup_state_decides_how_the_setting_looks( StartupState state, bool available, bool canChange, bool locked, bool enabled) { var startup = new FakeStartupService { State = state }; using var viewModel = Create(startup: startup); await viewModel.InitializeAsync(); Assert.Equal(available, viewModel.IsStartupAvailable); Assert.Equal(canChange, viewModel.CanChangeStartup); Assert.Equal(locked, viewModel.IsStartupLocked); Assert.Equal(enabled, viewModel.RunAtStartup); } [Fact] public async Task The_startup_setting_is_announced_after_Windows_answers() { var startup = new FakeStartupService { State = StartupState.Enabled }; using var viewModel = Create(startup: startup); List changed = []; viewModel.PropertyChanged += (_, e) => changed.Add(e.PropertyName); await viewModel.InitializeAsync(); Assert.Contains(nameof(SettingsViewModel.RunAtStartup), changed); Assert.Contains(nameof(SettingsViewModel.IsStartupAvailable), changed); Assert.Contains(nameof(SettingsViewModel.CanChangeStartup), changed); Assert.Contains(nameof(SettingsViewModel.IsStartupLocked), changed); } [Fact] public async Task Enabling_startup_reaches_Windows() { var startup = new FakeStartupService { State = StartupState.Disabled }; using var viewModel = Create(startup: startup); await viewModel.InitializeAsync(); viewModel.RunAtStartup = true; await WaitForStartupRequests(startup, 1); Assert.Equal([true], startup.Requests); Assert.True(viewModel.RunAtStartup); } [Fact] public async Task Disabling_startup_reaches_Windows() { var startup = new FakeStartupService { State = StartupState.Enabled }; using var viewModel = Create(startup: startup); await viewModel.InitializeAsync(); viewModel.RunAtStartup = false; await WaitForStartupRequests(startup, 1); Assert.Equal([false], startup.Requests); Assert.False(viewModel.RunAtStartup); } [Fact] public async Task Setting_the_same_value_again_leaves_Windows_alone() { var startup = new FakeStartupService { State = StartupState.Enabled }; using var viewModel = Create(startup: startup); await viewModel.InitializeAsync(); viewModel.RunAtStartup = true; Assert.Empty(startup.Requests); } // A ban by the user is not for the app to argue with: the tick has to come back [Fact] public async Task A_refused_request_puts_the_tick_back() { var startup = new FakeStartupService { State = StartupState.Disabled, AnswerOnEnable = StartupState.DisabledByUser, }; using var viewModel = Create(startup: startup); await viewModel.InitializeAsync(); List changed = []; viewModel.PropertyChanged += (_, e) => changed.Add(e.PropertyName); viewModel.RunAtStartup = true; await WaitForStartupRequests(startup, 1); Assert.False(viewModel.RunAtStartup); Assert.True(viewModel.IsStartupLocked); Assert.Contains(nameof(SettingsViewModel.RunAtStartup), changed); } [Fact] public void Closing_unsubscribes_from_the_settings_and_the_language() { var settings = new AppSettings { Language = "en" }; var localization = new FakeLocalizationService(); SettingsViewModel viewModel = Create(settings, localization); EnumOption option = viewModel.Themes[0]; string display = option.Display; viewModel.Dispose(); settings.Language = "ru"; // Neither the interface language nor the option captions change any more Assert.Equal("en", localization.CurrentLanguage); Assert.Equal(display, option.Display); } private static SettingsViewModel Create( AppSettings? settings = null, ILocalizationService? localization = null, IStartupService? startup = null, Version? version = null) => new(settings ?? new AppSettings(), localization ?? new FakeLocalizationService(), startup ?? new FakeStartupService(), version ?? new Version(1, 0, 0, 0)); // The setting travels to Windows without being awaited: the window must not freeze private static async Task WaitForStartupRequests(FakeStartupService startup, int count) { for (int i = 0; i < 100 && startup.Requests.Count < count; i++) { await Task.Delay(5); } Assert.Equal(count, startup.Requests.Count); } }