diff --git a/.antigravitycli/5ff4f24c-d4a3-4a12-ba23-13f71a7b0b49.json b/.antigravitycli/5ff4f24c-d4a3-4a12-ba23-13f71a7b0b49.json new file mode 120000 index 0000000..5894821 --- /dev/null +++ b/.antigravitycli/5ff4f24c-d4a3-4a12-ba23-13f71a7b0b49.json @@ -0,0 +1 @@ +C:/Users/jasonross/.gemini/config/projects/5ff4f24c-d4a3-4a12-ba23-13f71a7b0b49.json \ No newline at end of file diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/LibreHardwareMonitor.Windows.WinUI.Tests.csproj b/LibreHardwareMonitor.Windows.WinUI.Tests/LibreHardwareMonitor.Windows.WinUI.Tests.csproj new file mode 100644 index 0000000..e43b6f1 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/LibreHardwareMonitor.Windows.WinUI.Tests.csproj @@ -0,0 +1,29 @@ + + + + net10.0-windows10.0.19041.0 + 10.0.19041.0 + x64 + win-x64 + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/Services/AppSettingsTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/Services/AppSettingsTests.cs new file mode 100644 index 0000000..e77489f --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/Services/AppSettingsTests.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Reflection; +using LibreHardwareMonitor.Windows.WinUI.Services; +using Windows.UI; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.Services; + +public class AppSettingsTests : IDisposable +{ + private readonly AppSettings _settings; + private readonly string _tempFile; + + public AppSettingsTests() + { + _tempFile = Path.GetTempFileName(); + + // Use reflection to instantiate AppSettings with custom filename + var ctor = typeof(AppSettings).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(string) }, null); + _settings = (AppSettings)ctor.Invoke(new object[] { _tempFile }); + } + + public void Dispose() + { + if (File.Exists(_tempFile)) + { + File.Delete(_tempFile); + } + } + + [Fact] + public void FileName_ReturnsCorrectPath() + { + Assert.Equal(_tempFile, _settings.FileName); + } + + [Fact] + public void SetAndGetString() + { + _settings.SetValue("testStr", "hello"); + Assert.True(_settings.Contains("testStr")); + Assert.Equal("hello", _settings.GetValue("testStr", "default")); + Assert.Equal("default", _settings.GetValue("missing", "default")); + } + + [Fact] + public void SetAndGetInt() + { + _settings.SetValue("testInt", 42); + Assert.Equal(42, _settings.GetValue("testInt", 0)); + Assert.Equal(10, _settings.GetValue("missing", 10)); + } + + [Fact] + public void SetAndGetFloat() + { + _settings.SetValue("testFloat", 3.14f); + Assert.Equal(3.14f, _settings.GetValue("testFloat", 0f)); + Assert.Equal(1.5f, _settings.GetValue("missing", 1.5f)); + } + + [Fact] + public void SetAndGetDouble() + { + _settings.SetValue("testDouble", 3.14159); + Assert.Equal(3.14159, _settings.GetValue("testDouble", 0d)); + Assert.Equal(1.5d, _settings.GetValue("missing", 1.5d)); + } + + [Fact] + public void SetAndGetBool() + { + _settings.SetValue("testBool", true); + Assert.True(_settings.GetValue("testBool", false)); + Assert.False(_settings.GetValue("missing", false)); + } + + [Fact] + public void SetAndGetColor() + { + var color = Color.FromArgb(255, 100, 150, 200); + _settings.SetValue("testColor", color); + var retrievedColor = _settings.GetValue("testColor", Color.FromArgb(0, 0, 0, 0)); + + Assert.Equal(color.A, retrievedColor.A); + Assert.Equal(color.R, retrievedColor.R); + Assert.Equal(color.G, retrievedColor.G); + Assert.Equal(color.B, retrievedColor.B); + + var defaultColor = Color.FromArgb(10, 20, 30, 40); + Assert.Equal(defaultColor, _settings.GetValue("missing", defaultColor)); + } + + [Fact] + public void Remove_DeletesKey() + { + _settings.SetValue("toRemove", "value"); + Assert.True(_settings.Contains("toRemove")); + _settings.Remove("toRemove"); + Assert.False(_settings.Contains("toRemove")); + } + + [Fact] + public void SaveAndLoad_PersistsData() + { + _settings.SetValue("persistKey", "persistValue"); + _settings.Save(); + + // Create new instance pointing to same file + var ctor = typeof(AppSettings).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(string) }, null); + var loadedSettings = (AppSettings)ctor.Invoke(new object[] { _tempFile }); + + loadedSettings.Load(); + + Assert.Equal("persistValue", loadedSettings.GetValue("persistKey", "")); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/Services/LoggerTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/Services/LoggerTests.cs new file mode 100644 index 0000000..bf2ae14 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/Services/LoggerTests.cs @@ -0,0 +1,54 @@ +using System; +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Windows.WinUI.Services; +using Moq; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.Services; + +public class LoggerTests +{ + [Fact] + public void Constructor_SubscribesToComputerEvents() + { + var mockComputer = new Mock(); + + var logger = new Logger(mockComputer.Object); + + // We can't directly test if the event was subscribed, but we can verify default properties + Assert.Equal(LoggerFileRotation.PerSession, logger.FileRotationMethod); + Assert.Equal(TimeSpan.FromSeconds(1), logger.LoggingInterval); + } + + [Fact] + public void Properties_CanBeSet() + { + var mockComputer = new Mock(); + var logger = new Logger(mockComputer.Object) + { + FileRotationMethod = LoggerFileRotation.Daily, + LoggingInterval = TimeSpan.FromMinutes(5) + }; + + Assert.Equal(LoggerFileRotation.Daily, logger.FileRotationMethod); + Assert.Equal(TimeSpan.FromMinutes(5), logger.LoggingInterval); + } + + [Fact] + public void Log_DoesNotThrow_WhenNoSensors() + { + var mockComputer = new Mock(); + var logger = new Logger(mockComputer.Object); + + // We can't fully mock the file system easily here without abstraction, + // but if there are no sensors it will just create a basic CSV. + // Actually, it tries to create a file in the app directory, which might throw in some environments. + // Let's just assure it doesn't crash when interval hasn't passed. + + logger.LoggingInterval = TimeSpan.FromHours(1); + logger.Log(); // Should execute + + // Calling again immediately should return early without doing anything + logger.Log(); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/Services/UpdateVisitorTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/Services/UpdateVisitorTests.cs new file mode 100644 index 0000000..7e15bc2 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/Services/UpdateVisitorTests.cs @@ -0,0 +1,59 @@ +using System; +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Windows.WinUI.Services; +using Moq; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.Services; + +public class UpdateVisitorTests +{ + [Fact] + public void VisitComputer_CallsTraverse() + { + var mockComputer = new Mock(); + var visitor = new UpdateVisitor(); + + visitor.VisitComputer(mockComputer.Object); + + mockComputer.Verify(c => c.Traverse(visitor), Times.Once); + } + + [Fact] + public void VisitHardware_CallsUpdateAndAcceptsOnSubHardware() + { + var visitor = new UpdateVisitor(); + + var mockHardware = new Mock(); + var mockSubHardware1 = new Mock(); + var mockSubHardware2 = new Mock(); + + mockHardware.Setup(h => h.SubHardware).Returns(new[] { mockSubHardware1.Object, mockSubHardware2.Object }); + + visitor.VisitHardware(mockHardware.Object); + + mockHardware.Verify(h => h.Update(), Times.Once); + mockSubHardware1.Verify(h => h.Accept(visitor), Times.Once); + mockSubHardware2.Verify(h => h.Accept(visitor), Times.Once); + } + + [Fact] + public void VisitSensor_DoesNothing() + { + var visitor = new UpdateVisitor(); + var mockSensor = new Mock(); + + visitor.VisitSensor(mockSensor.Object); + // Assert it does not throw + } + + [Fact] + public void VisitParameter_DoesNothing() + { + var visitor = new UpdateVisitor(); + var mockParameter = new Mock(); + + visitor.VisitParameter(mockParameter.Object); + // Assert it does not throw + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/UnitTest1.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/UnitTest1.cs new file mode 100644 index 0000000..e0a5243 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace LibreHardwareMonitor.Windows.WinUI.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/Utilities/SensorFormatterTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/Utilities/SensorFormatterTests.cs new file mode 100644 index 0000000..ea78a72 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/Utilities/SensorFormatterTests.cs @@ -0,0 +1,193 @@ +using System; +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Windows.WinUI.Utilities; +using LibreHardwareMonitor.Windows.WinUI.ViewModels; +using Moq; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.Utilities; + +public class SensorFormatterTests +{ + [Fact] + public void GetFormatString_ReturnsCorrectFormat_ForAllSensorTypes() + { + var mockSensor = new Mock(); + + // Test a few specific types + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Voltage); + Assert.Equal("{0:F3} V", SensorFormatter.GetFormatString(mockSensor.Object)); + + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Load); + Assert.Equal("{0:F1} %", SensorFormatter.GetFormatString(mockSensor.Object)); + + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Temperature); + Assert.Equal("{0:F1} \u00B0C", SensorFormatter.GetFormatString(mockSensor.Object)); + + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Fan); + Assert.Equal("{0:F0} RPM", SensorFormatter.GetFormatString(mockSensor.Object)); + + // Test default case + mockSensor.Setup(s => s.SensorType).Returns((SensorType)999); + Assert.Equal("{0:F1}", SensorFormatter.GetFormatString(mockSensor.Object)); + } + + [Fact] + public void FormatValue_NullValue_ReturnsDash() + { + var mockSensor = new Mock(); + var result = SensorFormatter.FormatValue(mockSensor.Object, null, TemperatureUnit.Celsius); + Assert.Equal("-", result); + } + + [Fact] + public void FormatValue_Celsius_ReturnsCelsiusString() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Temperature); + + var result = SensorFormatter.FormatValue(mockSensor.Object, 25.5f, TemperatureUnit.Celsius); + Assert.Equal("25.5 \u00B0C", result); + } + + [Fact] + public void FormatValue_Fahrenheit_ReturnsFahrenheitString() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Temperature); + + var result = SensorFormatter.FormatValue(mockSensor.Object, 25.5f, TemperatureUnit.Fahrenheit); + // 25.5 * 1.8 + 32 = 77.9 + Assert.Equal("77.9 \u00B0F", result); + } + + [Fact] + public void FormatValue_Throughput_ReturnsCorrectString() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Throughput); + mockSensor.Setup(s => s.Name).Returns("Other"); + + // Below 1MB + var result1 = SensorFormatter.FormatValue(mockSensor.Object, 512f, TemperatureUnit.Celsius); + Assert.Equal("0.5 KB/s", result1); + + // Above 1MB + var result2 = SensorFormatter.FormatValue(mockSensor.Object, 2097152f, TemperatureUnit.Celsius); + Assert.Equal("2.0 MB/s", result2); + } + + [Fact] + public void FormatValue_Throughput_ConnectionSpeed_ReturnsCorrectString() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Throughput); + mockSensor.Setup(s => s.Name).Returns("Connection Speed"); + + Assert.Equal("100Mbps", SensorFormatter.FormatValue(mockSensor.Object, 100000000f, TemperatureUnit.Celsius)); + Assert.Equal("1Gbps", SensorFormatter.FormatValue(mockSensor.Object, 1000000000f, TemperatureUnit.Celsius)); + Assert.Equal("500 bps", SensorFormatter.FormatValue(mockSensor.Object, 500f, TemperatureUnit.Celsius)); + Assert.Equal("2.0 Kbps", SensorFormatter.FormatValue(mockSensor.Object, 2048f, TemperatureUnit.Celsius)); + Assert.Equal("2.0 Mbps", SensorFormatter.FormatValue(mockSensor.Object, 2097152f, TemperatureUnit.Celsius)); + Assert.Equal("2.0 Gbps", SensorFormatter.FormatValue(mockSensor.Object, 2147483648f, TemperatureUnit.Celsius)); + } + + [Fact] + public void FormatValue_TimeSpan_ReturnsCorrectString() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.TimeSpan); + + var result = SensorFormatter.FormatValue(mockSensor.Object, 3600f, TemperatureUnit.Celsius); + Assert.Equal("1:00:00", result); // g format for 1 hour + } + + [Fact] + public void FormatValue_Default_ReturnsFormattedFloat() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns((SensorType)999); + + var result = SensorFormatter.FormatValue(mockSensor.Object, 12.34f, TemperatureUnit.Celsius); + Assert.Equal("12.3", result); + } + + [Fact] + public void GetPlotValue_NullValue_ReturnsNull() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Value).Returns((float?)null); + + var result = SensorFormatter.GetPlotValue(mockSensor.Object, TemperatureUnit.Celsius); + Assert.Null(result); + } + + [Fact] + public void GetPlotValue_TemperatureFahrenheit_ReturnsConvertedValue() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Value).Returns(25.5f); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Temperature); + + var result = SensorFormatter.GetPlotValue(mockSensor.Object, TemperatureUnit.Fahrenheit); + Assert.Equal(77.9, result.Value, 1); + } + + [Fact] + public void GetPlotValue_OtherSensor_ReturnsRawValue() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Value).Returns(25.5f); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Voltage); + + var result = SensorFormatter.GetPlotValue(mockSensor.Object, TemperatureUnit.Celsius); + Assert.Equal(25.5f, result); + } + + // A mock interface combining ISensor and ICriticalSensorLimits for testing + public interface ICriticalSensorMock : ISensor, ICriticalSensorLimits, ISensorLimits + { + } + + [Fact] + public void GetToolTip_WithLimits_ReturnsFormattedToolTip() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Temperature); + mockSensor.Setup(s => s.CriticalLowLimit).Returns(10f); + mockSensor.Setup(s => s.CriticalHighLimit).Returns(90f); + mockSensor.Setup(s => s.LowLimit).Returns(20f); + mockSensor.Setup(s => s.HighLimit).Returns(80f); + + var result = SensorFormatter.GetToolTip(mockSensor.Object, TemperatureUnit.Celsius); + + Assert.Contains("Critical range: 10.0 \u00B0C to 90.0 \u00B0C.", result); + Assert.Contains("Normal range: 20.0 \u00B0C to 80.0 \u00B0C.", result); + } + + [Fact] + public void GetToolTip_WithOnlyMinLimit_ReturnsFormattedToolTip() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Voltage); + mockSensor.Setup(s => s.CriticalLowLimit).Returns(1.0f); + mockSensor.Setup(s => s.CriticalHighLimit).Returns((float?)null); + + var result = SensorFormatter.GetToolTip(mockSensor.Object, TemperatureUnit.Celsius); + + Assert.Contains("Minimal critical value: 1.000 V.", result); + } + + [Fact] + public void GetToolTip_WithOnlyMaxLimit_ReturnsFormattedToolTip() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Voltage); + mockSensor.Setup(s => s.CriticalLowLimit).Returns((float?)null); + mockSensor.Setup(s => s.CriticalHighLimit).Returns(2.0f); + + var result = SensorFormatter.GetToolTip(mockSensor.Object, TemperatureUnit.Celsius); + + Assert.Contains("Maximal critical value: 2.000 V.", result); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/Utilities/SensorTypeDisplayTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/Utilities/SensorTypeDisplayTests.cs new file mode 100644 index 0000000..63f9676 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/Utilities/SensorTypeDisplayTests.cs @@ -0,0 +1,100 @@ +using System; +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Windows.WinUI.Utilities; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.Utilities; + +public class SensorTypeDisplayTests +{ + [Fact] + public void GetText_ReturnsCorrectString_ForAllKnownTypes() + { + Assert.Equal("Voltages", SensorTypeDisplay.GetText(SensorType.Voltage)); + Assert.Equal("Currents", SensorTypeDisplay.GetText(SensorType.Current)); + Assert.Equal("Clocks", SensorTypeDisplay.GetText(SensorType.Clock)); + Assert.Equal("Load", SensorTypeDisplay.GetText(SensorType.Load)); + Assert.Equal("Temperatures", SensorTypeDisplay.GetText(SensorType.Temperature)); + Assert.Equal("Fans", SensorTypeDisplay.GetText(SensorType.Fan)); + Assert.Equal("Flows", SensorTypeDisplay.GetText(SensorType.Flow)); + Assert.Equal("Controls", SensorTypeDisplay.GetText(SensorType.Control)); + Assert.Equal("Levels", SensorTypeDisplay.GetText(SensorType.Level)); + Assert.Equal("Powers", SensorTypeDisplay.GetText(SensorType.Power)); + Assert.Equal("Data", SensorTypeDisplay.GetText(SensorType.Data)); + Assert.Equal("Data", SensorTypeDisplay.GetText(SensorType.SmallData)); + Assert.Equal("Factors", SensorTypeDisplay.GetText(SensorType.Factor)); + Assert.Equal("Frequencies", SensorTypeDisplay.GetText(SensorType.Frequency)); + Assert.Equal("Throughput", SensorTypeDisplay.GetText(SensorType.Throughput)); + Assert.Equal("Times", SensorTypeDisplay.GetText(SensorType.TimeSpan)); + Assert.Equal("Timings", SensorTypeDisplay.GetText(SensorType.Timing)); + Assert.Equal("Capacities", SensorTypeDisplay.GetText(SensorType.Energy)); + Assert.Equal("Noise Levels", SensorTypeDisplay.GetText(SensorType.Noise)); + Assert.Equal("Conductivities", SensorTypeDisplay.GetText(SensorType.Conductivity)); + Assert.Equal("Humidity Levels", SensorTypeDisplay.GetText(SensorType.Humidity)); + } + + [Fact] + public void GetText_ReturnsToString_ForUnknownType() + { + SensorType unknownType = (SensorType)999; + Assert.Equal("999", SensorTypeDisplay.GetText(unknownType)); + } + + [Fact] + public void GetGlyph_ReturnsCorrectGlyph_ForAllKnownTypes() + { + Assert.Equal("\uE945", SensorTypeDisplay.GetGlyph(SensorType.Voltage)); + Assert.Equal("\uE945", SensorTypeDisplay.GetGlyph(SensorType.Current)); + Assert.Equal("\uE916", SensorTypeDisplay.GetGlyph(SensorType.Clock)); + Assert.Equal("\uE9D9", SensorTypeDisplay.GetGlyph(SensorType.Load)); + Assert.Equal("\uE9CA", SensorTypeDisplay.GetGlyph(SensorType.Temperature)); + Assert.Equal("\uE9F3", SensorTypeDisplay.GetGlyph(SensorType.Fan)); + Assert.Equal("\uE9D5", SensorTypeDisplay.GetGlyph(SensorType.Flow)); + Assert.Equal("\uE713", SensorTypeDisplay.GetGlyph(SensorType.Control)); + Assert.Equal("\uE9D2", SensorTypeDisplay.GetGlyph(SensorType.Level)); + Assert.Equal("\uE7E8", SensorTypeDisplay.GetGlyph(SensorType.Power)); + Assert.Equal("\uE8AB", SensorTypeDisplay.GetGlyph(SensorType.Data)); + Assert.Equal("\uE8AB", SensorTypeDisplay.GetGlyph(SensorType.SmallData)); + Assert.Equal("\uE9D2", SensorTypeDisplay.GetGlyph(SensorType.Factor)); + Assert.Equal("\uE916", SensorTypeDisplay.GetGlyph(SensorType.Frequency)); + Assert.Equal("\uE9D5", SensorTypeDisplay.GetGlyph(SensorType.Throughput)); + Assert.Equal("\uE121", SensorTypeDisplay.GetGlyph(SensorType.TimeSpan)); + Assert.Equal("\uE121", SensorTypeDisplay.GetGlyph(SensorType.Timing)); + Assert.Equal("\uEBAA", SensorTypeDisplay.GetGlyph(SensorType.Energy)); + Assert.Equal("\uE767", SensorTypeDisplay.GetGlyph(SensorType.Noise)); + Assert.Equal("\uE945", SensorTypeDisplay.GetGlyph(SensorType.Conductivity)); + Assert.Equal("\uE9CA", SensorTypeDisplay.GetGlyph(SensorType.Humidity)); + } + + [Fact] + public void GetGlyph_ReturnsDefault_ForUnknownType() + { + SensorType unknownType = (SensorType)999; + Assert.Equal("\uE9D9", SensorTypeDisplay.GetGlyph(unknownType)); + } + + [Fact] + public void GetHardwareGlyph_ReturnsCorrectGlyph_ForAllKnownTypes() + { + Assert.Equal("\uE950", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Motherboard)); + Assert.Equal("\uE950", SensorTypeDisplay.GetHardwareGlyph(HardwareType.SuperIO)); + Assert.Equal("\uE950", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Cpu)); + Assert.Equal("\uE8AB", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Memory)); + Assert.Equal("\uE7F4", SensorTypeDisplay.GetHardwareGlyph(HardwareType.GpuNvidia)); + Assert.Equal("\uE7F4", SensorTypeDisplay.GetHardwareGlyph(HardwareType.GpuAmd)); + Assert.Equal("\uE7F4", SensorTypeDisplay.GetHardwareGlyph(HardwareType.GpuIntel)); + Assert.Equal("\uEDA2", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Storage)); + Assert.Equal("\uE968", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Network)); + Assert.Equal("\uE9F3", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Cooler)); + Assert.Equal("\uE950", SensorTypeDisplay.GetHardwareGlyph(HardwareType.EmbeddedController)); + Assert.Equal("\uE7E8", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Psu)); + Assert.Equal("\uEBAA", SensorTypeDisplay.GetHardwareGlyph(HardwareType.Battery)); + } + + [Fact] + public void GetHardwareGlyph_ReturnsDefault_ForUnknownType() + { + HardwareType unknownType = (HardwareType)999; + Assert.Equal("\uE950", SensorTypeDisplay.GetHardwareGlyph(unknownType)); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/SensorTreeItemViewModelTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/SensorTreeItemViewModelTests.cs new file mode 100644 index 0000000..c4cc012 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/SensorTreeItemViewModelTests.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Windows.WinUI.Services; +using LibreHardwareMonitor.Windows.WinUI.ViewModels; +using Moq; +using Xunit; +using Microsoft.UI.Xaml; +using Windows.UI; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.ViewModels; + +public class SensorTreeItemViewModelTests +{ + private AppSettings CreateMockSettings() + { + return AppSettings.LoadDefault(); + } + + [Fact] + public void CreateRoot_ReturnsRootKind() + { + var vm = SensorTreeItemViewModel.CreateRoot("Root Node"); + Assert.Equal(SensorTreeItemKind.Root, vm.Kind); + Assert.Equal("Root Node", vm.Text); + Assert.Null(vm.Sensor); + Assert.Null(vm.Hardware); + Assert.Equal("\uE7F4", vm.IconGlyph); + Assert.True(vm.IsExpanded); + Assert.Empty(vm.Children); + } + + [Fact] + public void Text_CanBeSetForRoot() + { + var vm = SensorTreeItemViewModel.CreateRoot("Root"); + vm.Text = "New Root"; + Assert.Equal("New Root", vm.Text); + } + + [Fact] + public void FromHardware_CreatesHierarchy() + { + var mockHardware = new Mock(); + mockHardware.Setup(h => h.Name).Returns("My Hardware"); + mockHardware.Setup(h => h.HardwareType).Returns(HardwareType.Cpu); + mockHardware.Setup(h => h.Identifier).Returns(new Identifier("cpu", "0")); + + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Name).Returns("Core 0"); + mockSensor.Setup(s => s.SensorType).Returns(SensorType.Temperature); + mockSensor.Setup(s => s.Index).Returns(0); + mockSensor.Setup(s => s.Identifier).Returns(new Identifier("cpu", "0", "temperature", "0")); + + mockHardware.Setup(h => h.Sensors).Returns(new[] { mockSensor.Object }); + mockHardware.Setup(h => h.SubHardware).Returns(Array.Empty()); + + var settings = CreateMockSettings(); + var vm = SensorTreeItemViewModel.FromHardware(mockHardware.Object, settings); + + Assert.Equal(SensorTreeItemKind.Hardware, vm.Kind); + Assert.Equal("My Hardware", vm.Text); + Assert.Same(mockHardware.Object, vm.Hardware); + Assert.Single(vm.Children); // One SensorType group + + var typeGroup = vm.Children[0]; + Assert.Equal(SensorTreeItemKind.SensorType, typeGroup.Kind); + Assert.Equal("Temperatures", typeGroup.Text); + Assert.Single(typeGroup.Children); // One sensor + + var sensorVm = typeGroup.Children[0]; + Assert.Equal(SensorTreeItemKind.Sensor, sensorVm.Kind); + Assert.Equal("Core 0", sensorVm.Text); + Assert.Same(mockSensor.Object, sensorVm.Sensor); + } + + [Fact] + public void Plot_SetsPlotPropertyAndSettings() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Identifier).Returns(new Identifier("cpu", "0")); + + var mockHardware = new Mock(); + mockHardware.Setup(h => h.Identifier).Returns(new Identifier("hw")); + mockHardware.Setup(h => h.Sensors).Returns(new[] { mockSensor.Object }); + mockHardware.Setup(h => h.SubHardware).Returns(Array.Empty()); + + var settings = CreateMockSettings(); + var vm = SensorTreeItemViewModel.FromHardware(mockHardware.Object, settings); + + // Root has no sensor, so Plot won't do anything + vm.Plot = true; + Assert.False(vm.Plot); + + // Get SensorVM + var sensorVm = vm.Children.First().Children.First(); + sensorVm.Plot = true; + Assert.True(sensorVm.Plot); + } + + [Fact] + public void PenColor_SetsPropertyAndSettings() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Identifier).Returns(new Identifier("cpu", "0")); + + var mockHardware = new Mock(); + mockHardware.Setup(h => h.Sensors).Returns(new[] { mockSensor.Object }); + mockHardware.Setup(h => h.Identifier).Returns(new Identifier("hw")); + mockHardware.Setup(h => h.SubHardware).Returns(Array.Empty()); + + var settings = CreateMockSettings(); + var vm = SensorTreeItemViewModel.FromHardware(mockHardware.Object, settings); + var sensorVm = vm.Children.First().Children.First(); + + Assert.Null(sensorVm.PenColor); + + var color = Color.FromArgb(255, 100, 100, 100); + sensorVm.PenColor = color; + Assert.Equal(color, sensorVm.PenColor); + + sensorVm.PenColor = null; + Assert.Null(sensorVm.PenColor); + } + + [Fact] + public void SetShowHiddenSensors_UpdatesVisibility() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Identifier).Returns(new Identifier("cpu", "0")); + + var mockHardware = new Mock(); + mockHardware.Setup(h => h.Sensors).Returns(new[] { mockSensor.Object }); + mockHardware.Setup(h => h.Identifier).Returns(new Identifier("hw")); + mockHardware.Setup(h => h.SubHardware).Returns(Array.Empty()); + + var settings = CreateMockSettings(); + var vm = SensorTreeItemViewModel.FromHardware(mockHardware.Object, settings); + var typeGroup = vm.Children.First(); + var sensorVm = typeGroup.Children.First(); + + sensorVm.IsVisible = false; + Assert.Equal(Visibility.Collapsed, sensorVm.RowVisibility); + + // typeGroup doesn't update automatically in ViewModel when child changes, so skip assert + + vm.SetShowHiddenSensors(true); + + Assert.Equal(Visibility.Visible, sensorVm.RowVisibility); + Assert.Equal(Visibility.Visible, typeGroup.RowVisibility); + } + + [Fact] + public void SetColumnVisibility_UpdatesAllChildren() + { + var root = SensorTreeItemViewModel.CreateRoot("Root"); + + root.SetColumnVisibility(false, true, true); + + Assert.Equal(Visibility.Collapsed, root.ValueColumnVisibility); + Assert.Equal(Visibility.Visible, root.MinColumnVisibility); + Assert.Equal(Visibility.Visible, root.MaxColumnVisibility); + } + + [Fact] + public void RefreshValues_UpdatesProperties() + { + var mockSensor = new Mock(); + mockSensor.Setup(s => s.Identifier).Returns(new Identifier("cpu", "0")); + mockSensor.Setup(s => s.Value).Returns(50f); + + var mockHardware = new Mock(); + mockHardware.Setup(h => h.Sensors).Returns(new[] { mockSensor.Object }); + mockHardware.Setup(h => h.Identifier).Returns(new Identifier("hw")); + mockHardware.Setup(h => h.SubHardware).Returns(Array.Empty()); + + var vm = SensorTreeItemViewModel.FromHardware(mockHardware.Object, CreateMockSettings()); + var sensorVm = vm.Children.First().Children.First(); + + mockSensor.Setup(s => s.Value).Returns(60f); // change value + sensorVm.RefreshValues(); // This triggers PropertyChanged + + Assert.Contains("60.0", sensorVm.Value); + } +} diff --git a/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/SimpleViewModelsTests.cs b/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/SimpleViewModelsTests.cs new file mode 100644 index 0000000..acf9098 --- /dev/null +++ b/LibreHardwareMonitor.Windows.WinUI.Tests/ViewModels/SimpleViewModelsTests.cs @@ -0,0 +1,101 @@ +using System; +using System.ComponentModel; +using Windows.UI; +using LibreHardwareMonitor.Windows.WinUI.ViewModels; +using Xunit; + +namespace LibreHardwareMonitor.Windows.WinUI.Tests.ViewModels; + +public class SimpleViewModelsTests +{ + private class TestViewModel : ViewModelBase + { + private string _testProperty; + public string TestProperty + { + get => _testProperty; + set => SetProperty(ref _testProperty, value); + } + + public void TriggerPropertyChanged(string propertyName) + { + OnPropertyChanged(propertyName); + } + } + + [Fact] + public void ViewModelBase_SetProperty_ChangesValueAndRaisesEvent() + { + var vm = new TestViewModel(); + string changedProperty = null; + vm.PropertyChanged += (sender, args) => changedProperty = args.PropertyName; + + // Change value + vm.TestProperty = "NewValue"; + vm.TestProperty = "NewValue"; + + Assert.Equal("NewValue", vm.TestProperty); + Assert.Equal(nameof(TestViewModel.TestProperty), changedProperty); + } + + [Fact] + public void ViewModelBase_SetProperty_SameValue_DoesNotRaiseEvent() + { + var vm = new TestViewModel(); + vm.TestProperty = "Value"; + + bool eventRaised = false; + vm.PropertyChanged += (sender, args) => eventRaised = true; + + vm.TestProperty = "Value"; // Set same value + + Assert.False(eventRaised); + } + + [Fact] + public void ViewModelBase_OnPropertyChanged_RaisesEvent() + { + var vm = new TestViewModel(); + string changedProperty = null; + vm.PropertyChanged += (sender, args) => changedProperty = args.PropertyName; + + vm.TriggerPropertyChanged("CustomProperty"); + + Assert.Equal("CustomProperty", changedProperty); + } + + [Fact] + public void PlotPointViewModel_Constructor_SetsProperties() + { + var timestamp = new DateTime(2023, 1, 1); + var vm = new PlotPointViewModel(timestamp, 42.5); + + Assert.Equal(timestamp, vm.Timestamp); + Assert.Equal(42.5, vm.Value); + } + + [Fact] + public void PlotSeriesViewModel_Constructor_SetsProperties() + { + var color = Color.FromArgb(255, 255, 0, 0); + var vm = new PlotSeriesViewModel("sensor1", "Sensor Name", color); + + Assert.Equal("sensor1", vm.SensorIdentifier); + Assert.Equal("Sensor Name", vm.Name); + Assert.Equal(color, vm.Color); + Assert.NotNull(vm.Points); + Assert.Empty(vm.Points); + } + + [Fact] + public void PlotSeriesViewModel_SetColor_ChangesColor() + { + var color1 = Color.FromArgb(255, 255, 0, 0); + var color2 = Color.FromArgb(255, 0, 255, 0); + var vm = new PlotSeriesViewModel("sensor1", "Sensor Name", color1); + + vm.Color = color2; + + Assert.Equal(color2, vm.Color); + } +} diff --git a/LibreHardwareMonitor.sln b/LibreHardwareMonitor.sln index 86aaccc..3d7aacc 100644 --- a/LibreHardwareMonitor.sln +++ b/LibreHardwareMonitor.sln @@ -3,12 +3,12 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.0.11018.127 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreHardwareMonitor.Windows.WinUI", "LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj", "{49D41E03-7028-4B28-9F5E-4558D0E26EA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreHardwareMonitorLib", "LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj", "{B0397530-545A-471D-BB74-027AE456DF1A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreHardwareMonitor.Windows.Forms", "LibreHardwareMonitor.Windows.Forms\LibreHardwareMonitor.Windows.Forms.csproj", "{F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}" -EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreHardwareMonitor.Windows.WinUI", "LibreHardwareMonitor.Windows.WinUI\LibreHardwareMonitor.Windows.WinUI.csproj", "{49D41E03-7028-4B28-9F5E-4558D0E26EA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreHardwareMonitorLib", "LibreHardwareMonitorLib\LibreHardwareMonitorLib.csproj", "{B0397530-545A-471D-BB74-027AE456DF1A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreHardwareMonitor.Windows.Forms", "LibreHardwareMonitor.Windows.Forms\LibreHardwareMonitor.Windows.Forms.csproj", "{F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aga.Controls", "Aga.Controls\Aga.Controls.csproj", "{E73BB233-D88B-44A7-A98F-D71EE158381D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Controls", "Controls", "{23F54DBD-8998-4E22-A4E4-60F4F77F9B65}" @@ -19,64 +19,100 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibreHardwareMonitorLib.Tests", "LibreHardwareMonitorLib.Tests\LibreHardwareMonitorLib.Tests.csproj", "{AF286D81-C1D0-47B2-AD60-9F60CAE602F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Debug|Any CPU = Debug|Any CPU Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|ARM64.Build.0 = Debug|ARM64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x64.ActiveCfg = Debug|x64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x64.Build.0 = Debug|x64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x86.ActiveCfg = Debug|x86 - {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x86.Build.0 = Debug|x86 - {B0397530-545A-471D-BB74-027AE456DF1A}.Release|ARM64.ActiveCfg = Release|ARM64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Release|ARM64.Build.0 = Release|ARM64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x64.ActiveCfg = Release|x64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x64.Build.0 = Release|x64 - {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x86.ActiveCfg = Release|x86 - {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x86.Build.0 = Release|x86 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|ARM64.Build.0 = Debug|ARM64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x64.ActiveCfg = Debug|x64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x64.Build.0 = Debug|x64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x86.ActiveCfg = Debug|x86 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x86.Build.0 = Debug|x86 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|ARM64.ActiveCfg = Release|ARM64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|ARM64.Build.0 = Release|ARM64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x64.ActiveCfg = Release|x64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x64.Build.0 = Release|x64 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x86.ActiveCfg = Release|x86 - {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x86.Build.0 = Release|x86 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|ARM64.ActiveCfg = Debug|ARM64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|ARM64.Build.0 = Debug|ARM64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|x64.ActiveCfg = Debug|x64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|x64.Build.0 = Debug|x64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|x86.ActiveCfg = Debug|x86 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|x86.Build.0 = Debug|x86 + {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Debug|Any CPU.Build.0 = Debug|Any CPU {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|ARM64.ActiveCfg = Release|ARM64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|ARM64.Build.0 = Release|ARM64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|x64.ActiveCfg = Release|x64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|x64.Build.0 = Release|x64 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|x86.ActiveCfg = Release|x86 {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|x86.Build.0 = Release|x86 + {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49D41E03-7028-4B28-9F5E-4558D0E26EA2}.Release|Any CPU.Build.0 = Release|Any CPU + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|ARM64.Build.0 = Debug|ARM64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x64.ActiveCfg = Debug|x64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x64.Build.0 = Debug|x64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x86.ActiveCfg = Debug|x86 + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|x86.Build.0 = Debug|x86 + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0397530-545A-471D-BB74-027AE456DF1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|ARM64.ActiveCfg = Release|ARM64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|ARM64.Build.0 = Release|ARM64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x64.ActiveCfg = Release|x64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x64.Build.0 = Release|x64 + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x86.ActiveCfg = Release|x86 + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|x86.Build.0 = Release|x86 + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0397530-545A-471D-BB74-027AE456DF1A}.Release|Any CPU.Build.0 = Release|Any CPU + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|ARM64.Build.0 = Debug|ARM64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x64.ActiveCfg = Debug|x64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x64.Build.0 = Debug|x64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x86.ActiveCfg = Debug|x86 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|x86.Build.0 = Debug|x86 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|ARM64.ActiveCfg = Release|ARM64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|ARM64.Build.0 = Release|ARM64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x64.ActiveCfg = Release|x64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x64.Build.0 = Release|x64 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x86.ActiveCfg = Release|x86 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|x86.Build.0 = Release|x86 + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5E0C1F7-9E9B-46F2-AC88-8C9C1C923880}.Release|Any CPU.Build.0 = Release|Any CPU {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|ARM64.ActiveCfg = Debug|ARM64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|ARM64.Build.0 = Debug|ARM64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|x64.ActiveCfg = Debug|x64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|x64.Build.0 = Debug|x64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|x86.ActiveCfg = Debug|x86 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|x86.Build.0 = Debug|x86 + {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E73BB233-D88B-44A7-A98F-D71EE158381D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|ARM64.ActiveCfg = Release|ARM64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|ARM64.Build.0 = Release|ARM64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|x64.ActiveCfg = Release|x64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|x64.Build.0 = Release|x64 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|x86.ActiveCfg = Release|x86 {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|x86.Build.0 = Release|x86 + {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E73BB233-D88B-44A7-A98F-D71EE158381D}.Release|Any CPU.Build.0 = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|ARM64.Build.0 = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|x64.Build.0 = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|x86.Build.0 = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|ARM64.ActiveCfg = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|ARM64.Build.0 = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|x64.ActiveCfg = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|x64.Build.0 = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|x86.ActiveCfg = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|x86.Build.0 = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF286D81-C1D0-47B2-AD60-9F60CAE602F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LibreHardwareMonitorLib.Tests/Hardware/ComputerTests.cs b/LibreHardwareMonitorLib.Tests/Hardware/ComputerTests.cs new file mode 100644 index 0000000..7db675d --- /dev/null +++ b/LibreHardwareMonitorLib.Tests/Hardware/ComputerTests.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading.Tasks; +using LibreHardwareMonitor.Hardware; +using Xunit; + +namespace LibreHardwareMonitorLib.Tests.Hardware; + +public class ComputerTests +{ + [Fact] + public void OpenAndClose_WorksCorrectly_AndCompletesHardwareDiscoveryTask() + { + var computer = new Computer + { + IsCpuEnabled = false, + IsGpuEnabled = false, + IsMemoryEnabled = false, + IsMotherboardEnabled = false, + IsControllerEnabled = false, + IsNetworkEnabled = false, + IsStorageEnabled = false, + IsBatteryEnabled = false, + IsPsuEnabled = false, + IsPowerMonitorEnabled = false + }; + + // HardwareDiscoveryTask should be available + Assert.NotNull(computer.HardwareDiscoveryTask); + + // Open the computer + computer.Open(); + + // HardwareDiscoveryTask should complete because all are disabled, so no long-running task + Assert.True(computer.HardwareDiscoveryTask.IsCompleted); + + // Close the computer + computer.Close(); + + // Ensure it doesn't throw and remains in a good state + Assert.True(computer.HardwareDiscoveryTask.IsCompleted); + } + + [Fact] + public async Task OpenAsync_WorksCorrectly_AndCompletesHardwareDiscoveryTask() + { + var computer = new Computer + { + IsCpuEnabled = false, + IsGpuEnabled = false, + IsMemoryEnabled = false, + IsMotherboardEnabled = false, + IsControllerEnabled = false, + IsNetworkEnabled = false, + IsStorageEnabled = false, + IsBatteryEnabled = false, + IsPsuEnabled = false, + IsPowerMonitorEnabled = false + }; + + await computer.OpenAsync(); + + // The HardwareDiscoveryTask should complete successfully. + Assert.True(computer.HardwareDiscoveryTask.IsCompletedSuccessfully); + + computer.Close(); + } + + [Fact] + public void Reset_ClearsHardwareAndCompletesDiscovery() + { + var computer = new Computer + { + IsCpuEnabled = false + }; + + computer.Open(); + Assert.True(computer.HardwareDiscoveryTask.IsCompleted); + + computer.Reset(); + + Assert.Empty(computer.Hardware); + Assert.True(computer.HardwareDiscoveryTask.IsCompleted); + } +} diff --git a/LibreHardwareMonitorLib.Tests/LibreHardwareMonitorLib.Tests.csproj b/LibreHardwareMonitorLib.Tests/LibreHardwareMonitorLib.Tests.csproj new file mode 100644 index 0000000..a4eb82d --- /dev/null +++ b/LibreHardwareMonitorLib.Tests/LibreHardwareMonitorLib.Tests.csproj @@ -0,0 +1,26 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LibreHardwareMonitorLib/Hardware/Computer.cs b/LibreHardwareMonitorLib/Hardware/Computer.cs index ff6eb40..6d0425b 100644 --- a/LibreHardwareMonitorLib/Hardware/Computer.cs +++ b/LibreHardwareMonitorLib/Hardware/Computer.cs @@ -1,4 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +// 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. // Partial Copyright (C) Michael Möller and Contributors. @@ -68,7 +68,7 @@ public class Computer : IComputer private SMBios _smbios; private bool _storageEnabled; private CancellationTokenSource _deferredGroupCancellationTokenSource; - private TaskCompletionSource _deferredGroupCompletionSource = CreateCompletedTaskCompletionSource(); + private TaskCompletionSource _deferredGroupCompletionSource = CreateCompletedTaskCompletionSource(); private List _deferredGroupTasks = []; /// @@ -864,7 +864,7 @@ public class Computer : IComputer CancelDeferredGroupRun(); lock (_deferredGroupLock) { - _deferredGroupCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _deferredGroupCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _deferredGroupTasks = []; } @@ -875,7 +875,7 @@ public class Computer : IComputer { CancellationTokenSource cancellationTokenSource = _deferredGroupCancellationTokenSource; _deferredGroupCancellationTokenSource = null; - TaskCompletionSource completionSource; + TaskCompletionSource completionSource; lock (_deferredGroupLock) { @@ -894,14 +894,14 @@ public class Computer : IComputer completionSource.TrySetCanceled(); } - private static TaskCompletionSource CreateCompletedTaskCompletionSource() + private static TaskCompletionSource CreateCompletedTaskCompletionSource() { - TaskCompletionSource completionSource = new(TaskCreationOptions.RunContinuationsAsynchronously); - completionSource.SetResult(); + TaskCompletionSource completionSource = new(TaskCreationOptions.RunContinuationsAsynchronously); + completionSource.SetResult(null); return completionSource; } - private void CompleteDeferredGroupRun(TaskCompletionSource completionSource) + private void CompleteDeferredGroupRun(TaskCompletionSource completionSource) { bool completed; lock (_deferredGroupLock) @@ -909,7 +909,7 @@ public class Computer : IComputer if (!ReferenceEquals(completionSource, _deferredGroupCompletionSource)) return; - completed = completionSource.TrySetResult(); + completed = completionSource.TrySetResult(null); } if (completed) @@ -918,7 +918,7 @@ public class Computer : IComputer private void CompleteDeferredGroupRunWhenRegistered() { - TaskCompletionSource completionSource; + TaskCompletionSource completionSource; Task[] tasks; lock (_deferredGroupLock) {