using CursorLang.Core.Models; using CursorLang.Core.Services; using CursorLang.Settings.Services; using CursorLang.Settings.ViewModels; using CursorLang.Settings.Views; using Microsoft.Extensions.DependencyInjection; namespace CursorLang.Settings.Tests; /// /// What the container of the settings process is made of. The tests cannot build the /// application whole — it would raise a window and take the place of the single /// instance — but checking that everything needed is declared and resolvable works /// without that. /// public sealed class AppTests { [Theory] [InlineData(typeof(SettingsService))] [InlineData(typeof(ThemeService))] [InlineData(typeof(IThemeService))] [InlineData(typeof(MainWindowPlacement))] [InlineData(typeof(ILocalizationService))] [InlineData(typeof(IStartupService))] [InlineData(typeof(IUpdateService))] [InlineData(typeof(UpdateOptions))] [InlineData(typeof(SettingsViewModel))] [InlineData(typeof(UpdateViewModel))] [InlineData(typeof(MainWindow))] [InlineData(typeof(AppSettings))] public void Everything_the_window_needs_is_declared_in_the_container(Type service) { Assert.Contains(Describe(), descriptor => descriptor.ServiceType == service); } /// /// The background half is not in here, and must not be. /// /// /// The hook, the popup and the layout polling belong to the agent process now. A /// registration of any of them here would mean two applications watching the /// keyboard at once — and the second of them holding WPF while it did so. /// [Theory] [InlineData("KeyboardLayoutService")] [InlineData("LayoutPopupService")] [InlineData("CapsLockHotkeyService")] [InlineData("LayoutNotificationCoordinator")] [InlineData("CapsLockSwitchCoordinator")] [InlineData("TrayIcon")] public void The_background_half_is_not_in_the_settings_container(string name) { Assert.DoesNotContain(Describe(), descriptor => descriptor.ServiceType.Name.Contains(name)); } // The settings and the theme have to be shared by the whole window: a second copy // of them would mean lost edits or half the controls in the wrong colours [Fact] public void Everything_in_the_container_is_declared_as_a_single_copy() { Assert.All(Describe(), descriptor => Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime)); } [Fact] public void Every_service_is_declared_once() { var types = Describe().Select(descriptor => descriptor.ServiceType).ToList(); Assert.Equal(types.Count, types.Distinct().Count()); } /// /// The dependencies of every service have to be resolvable. The check runs /// while the container is built and creates no services itself. /// [Fact] public void The_service_dependencies_come_together_with_nothing_missing() { var services = new ServiceCollection(); App.ConfigureServices(services); ServiceProvider provider = services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true, ValidateScopes = true, }); // Ensure the provider was built successfully — this serves as an assertion // so the test framework recognizes this as a meaningful test. Assert.NotNull(provider); provider.Dispose(); } [Fact] public void The_settings_come_from_the_settings_service() { ServiceDescriptor settings = Describe() .Single(descriptor => descriptor.ServiceType == typeof(AppSettings)); // The settings are not created anew but read from disk by the service Assert.NotNull(settings.ImplementationFactory); } [Fact] public void The_theme_and_its_interface_are_one_service() { ServiceDescriptor theme = Describe() .Single(descriptor => descriptor.ServiceType == typeof(IThemeService)); Assert.NotNull(theme.ImplementationFactory); } private static ServiceCollection Describe() { var services = new ServiceCollection(); App.ConfigureServices(services); return services; } }