diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/Composition/ServiceCollectionExtensionsTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/Composition/ServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000..42d4d8e --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/Composition/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,49 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System.Linq; +using LibreHardwareMonitor.Windows.WinUI.Composition; +using LibreHardwareMonitor.Windows.WinUI.Services; +using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; +using LibreHardwareMonitor.Windows.WinUI.ViewModels; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.Composition; + +// Guards the single composition root: every dependency referenced by a registered service must +// itself be registered, otherwise the app fails to resolve its graph at launch. ValidateOnBuild +// builds the dependency call sites without instantiating the services, so this stays off the real +// hardware-monitor / kernel-driver path while still catching missing registrations. +public class ServiceCollectionExtensionsTests +{ + [Fact] + public void AddAppServices_BuildsProviderWithoutMissingRegistrations() + { + ServiceProvider provider = new ServiceCollection() + .AddAppServices(NoOpStartupTracer.Instance) + .BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); + + Assert.NotNull(provider); + provider.Dispose(); + } + + [Theory] + [InlineData(typeof(AppSettings))] + [InlineData(typeof(IStartupTracer))] + [InlineData(typeof(IHardwareMonitorService))] + [InlineData(typeof(ILogger))] + [InlineData(typeof(IRemoteWebServer))] + [InlineData(typeof(SensorSelectionService))] + [InlineData(typeof(PlotTrackingService))] + [InlineData(typeof(StartupService))] + [InlineData(typeof(MainWindowViewModel))] + [InlineData(typeof(IMainWindowRuntimeFactory))] + public void AddAppServices_RegistersExpectedService(System.Type serviceType) + { + IServiceCollection services = new ServiceCollection().AddAppServices(NoOpStartupTracer.Instance); + + Assert.Contains(services, descriptor => descriptor.ServiceType == serviceType); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/MainWindowViewModelTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/MainWindowViewModelTests.cs new file mode 100644 index 0000000..7f5ea74 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/MainWindowViewModelTests.cs @@ -0,0 +1,41 @@ +// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. +// Copyright (C) LibreHardwareMonitor and Contributors. + +using System; +using LibreHardwareMonitor.Windows.WinUI.Services; +using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; +using LibreHardwareMonitor.Windows.WinUI.ViewModels; +using Moq; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.ViewModels; + +// Demonstrates the headline win of the DI refactor: MainWindowViewModel can now be constructed with +// faked domain services (no real hardware monitor / kernel driver), and it reads its settings and +// wires the web-server root provider during construction. +public class MainWindowViewModelTests +{ + [Fact] + public void Constructor_WithFakedServices_ReadsSettingsAndWiresWebServerRoot() + { + AppSettings settings = AppSettings.LoadDefault(); + var hardwareMonitor = new Mock(); + var logger = new Mock(); + var webServer = new Mock(); + + using var viewModel = new MainWindowViewModel( + settings, + hardwareMonitor.Object, + logger.Object, + new SensorSelectionService(settings), + webServer.Object, + new PlotTrackingService(), + new StartupService(), + NoOpStartupTracer.Instance); + + Assert.True(viewModel.ShowValueColumn); + Assert.True(viewModel.ShowMaxColumn); + webServer.Verify(s => s.SetRootProvider(It.IsAny>()), Times.Once); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI/Composition/ServiceCollectionExtensions.cs b/LibreHardwareMonitor.Windows.WinUI/Composition/ServiceCollectionExtensions.cs index 14d9e0b..20a81c4 100644 --- a/LibreHardwareMonitor.Windows.WinUI/Composition/ServiceCollectionExtensions.cs +++ b/LibreHardwareMonitor.Windows.WinUI/Composition/ServiceCollectionExtensions.cs @@ -7,6 +7,7 @@ using LibreHardwareMonitor.Windows.WinUI.Services; using LibreHardwareMonitor.Windows.WinUI.Services.Tracing; using LibreHardwareMonitor.Windows.WinUI.ViewModels; using Microsoft.Extensions.DependencyInjection; +using Microsoft.UI.Dispatching; namespace LibreHardwareMonitor.Windows.WinUI.Composition; @@ -54,7 +55,8 @@ internal static class ServiceCollectionExtensions sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService())); + sp.GetRequiredService(), + DispatcherQueue.GetForCurrentThread())); // Runtime/window-tied service factory. The window itself is constructed by App (kept out of the // container so the XAML-tied Window is not a container-managed singleton). diff --git a/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs b/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs index a67bafa..3f6d081 100644 --- a/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs +++ b/LibreHardwareMonitor.Windows.WinUI/ViewModels/MainWindowViewModel.cs @@ -83,7 +83,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable ]; private readonly IHardwareMonitorService _hardwareMonitor; - private readonly DispatcherQueue _dispatcherQueue; + private readonly DispatcherQueue? _dispatcherQueue; private readonly ILogger _logger; private readonly PlotTrackingService _plotTracking; private readonly SensorSelectionService _sensorSelection; @@ -125,12 +125,13 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable IRemoteWebServer remoteWebServer, PlotTrackingService plotTracking, StartupService startupService, - IStartupTracer startupTrace) + IStartupTracer startupTrace, + DispatcherQueue? dispatcherQueue = null) { Settings = settings; _startupTrace = startupTrace; _startupTrace.Mark("MainWindowViewModel.Constructor.Begin"); - _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _dispatcherQueue = dispatcherQueue; _hardwareMonitor = hardwareMonitor; _logger = logger; _sensorSelection = sensorSelection; @@ -941,7 +942,7 @@ public sealed class MainWindowViewModel : ViewModelBase, IDisposable private void HardwareMonitor_TreeRebuilt(object? sender, EventArgs e) { - if (_dispatcherQueue.HasThreadAccess) + if (_dispatcherQueue is null || _dispatcherQueue.HasThreadAccess) { UpdateRoot(); UpdateStatus();