diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1c0903..8df6bc3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Fixed +- **ProcessManagerViewModel** — resolved CodeQL `cs/complex-condition` alert (#302) + by replacing chained null-conditional `||` expression with a `ReadOnlySpan` loop + in `MatchesDescription`. +- **PerformanceView** — eliminated MVVM violation: removed `PropertyChanged` + subscription and `Checked` event handler from code-behind; radio buttons now use + two-way `EqualityConverter` binding to `SelectedPlan` (pure XAML, no code-behind + logic). - **OperationLockServiceTests** — replaced flaky `Barrier` + `Thread.Sleep` thread-safety test with deterministic `CountdownEvent` + `ManualResetEventSlim` synchronization; asserts exactly 1 acquisition instead of `>= 1`. ### Added +- **EqualityConverter** — reusable two-way `IValueConverter` that compares a bound + value to `ConverterParameter`; ideal for radio button groups bound to a string + property. +- **EqualityConverterTests** — 10 unit tests covering Convert/ConvertBack, null + handling, and case sensitivity. - **FormatHelperTests** — 14 unit tests covering `FormatSize` at all boundaries (bytes, KB, MB, GB) with exact boundary and mid-range values. diff --git a/SysManager/SysManager.Tests/EqualityConverterTests.cs b/SysManager/SysManager.Tests/EqualityConverterTests.cs new file mode 100644 index 00000000..1dcd6bcd --- /dev/null +++ b/SysManager/SysManager.Tests/EqualityConverterTests.cs @@ -0,0 +1,82 @@ +// SysManager · EqualityConverterTests +// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager +// License: MIT + +using System.Globalization; +using System.Windows.Data; +using SysManager.Helpers; + +namespace SysManager.Tests; + +/// +/// Tests for . +/// +public class EqualityConverterTests +{ + private readonly EqualityConverter _sut = new(); + + [Theory] + [InlineData("balanced", "balanced", true)] + [InlineData("high", "high", true)] + [InlineData("ultimate", "ultimate", true)] + [InlineData("balanced", "high", false)] + [InlineData("high", "ultimate", false)] + [InlineData("", "balanced", false)] + public void Convert_ComparesValueToParameter(string value, string parameter, bool expected) + { + var result = _sut.Convert(value, typeof(bool), parameter, CultureInfo.InvariantCulture); + Assert.Equal(expected, result); + } + + [Fact] + public void Convert_NullValue_ReturnsFalse() + { + var result = _sut.Convert(null, typeof(bool), "balanced", CultureInfo.InvariantCulture); + Assert.Equal(false, result); + } + + [Fact] + public void Convert_NullParameter_ReturnsFalse() + { + var result = _sut.Convert("balanced", typeof(bool), null, CultureInfo.InvariantCulture); + Assert.Equal(false, result); + } + + [Fact] + public void Convert_BothNull_ReturnsFalse() + { + var result = _sut.Convert(null, typeof(bool), null, CultureInfo.InvariantCulture); + Assert.Equal(false, result); + } + + [Theory] + [InlineData("balanced")] + [InlineData("high")] + [InlineData("ultimate")] + public void ConvertBack_WhenTrue_ReturnsParameter(string parameter) + { + var result = _sut.ConvertBack(true, typeof(string), parameter, CultureInfo.InvariantCulture); + Assert.Equal(parameter, result); + } + + [Fact] + public void ConvertBack_WhenFalse_ReturnsDoNothing() + { + var result = _sut.ConvertBack(false, typeof(string), "balanced", CultureInfo.InvariantCulture); + Assert.Equal(Binding.DoNothing, result); + } + + [Fact] + public void ConvertBack_WhenNull_ReturnsDoNothing() + { + var result = _sut.ConvertBack(null, typeof(string), "balanced", CultureInfo.InvariantCulture); + Assert.Equal(Binding.DoNothing, result); + } + + [Fact] + public void Convert_IsCaseSensitive() + { + var result = _sut.Convert("Balanced", typeof(bool), "balanced", CultureInfo.InvariantCulture); + Assert.Equal(false, result); + } +} diff --git a/SysManager/SysManager/App.xaml b/SysManager/SysManager/App.xaml index 652c4b09..15ce1254 100644 --- a/SysManager/SysManager/App.xaml +++ b/SysManager/SysManager/App.xaml @@ -22,6 +22,7 @@ + True False diff --git a/SysManager/SysManager/Helpers/EqualityConverter.cs b/SysManager/SysManager/Helpers/EqualityConverter.cs new file mode 100644 index 00000000..fb2f6f7b --- /dev/null +++ b/SysManager/SysManager/Helpers/EqualityConverter.cs @@ -0,0 +1,31 @@ +// SysManager · EqualityConverter — two-way value equality for radio buttons +// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager +// License: MIT + +using System.Globalization; +using System.Windows.Data; + +namespace SysManager.Helpers; + +/// +/// Two-way converter that compares a bound value to ConverterParameter. +/// Convert: returns true when value equals parameter (for IsChecked). +/// ConvertBack: returns the parameter when IsChecked becomes true. +/// Usage: IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=balanced}" +/// +public sealed class EqualityConverter : IValueConverter +{ + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value == null || parameter == null) + return false; + return string.Equals(value.ToString(), parameter.ToString(), StringComparison.Ordinal); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is true) + return parameter?.ToString(); + return Binding.DoNothing; + } +} diff --git a/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs b/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs index 8e0d5774..3f69366a 100644 --- a/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs +++ b/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs @@ -165,10 +165,16 @@ private static bool MatchesFilter(ProcessEntry p, string filter) => private static bool MatchesName(ProcessEntry p, string filter) => p.Name.Contains(filter, StringComparison.OrdinalIgnoreCase); - private static bool MatchesDescription(ProcessEntry p, string filter) => - (p.Description?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) || - (p.PlainDescription?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) || - (p.Category?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false); + private static bool MatchesDescription(ProcessEntry p, string filter) + { + ReadOnlySpan fields = [p.Description, p.PlainDescription, p.Category]; + foreach (var field in fields) + { + if (field?.Contains(filter, StringComparison.OrdinalIgnoreCase) == true) + return true; + } + return false; + } private static bool MatchesPid(ProcessEntry p, string filter) => p.Pid.ToString().Contains(filter); diff --git a/SysManager/SysManager/Views/PerformanceView.xaml b/SysManager/SysManager/Views/PerformanceView.xaml index 870dc489..0d179135 100644 --- a/SysManager/SysManager/Views/PerformanceView.xaml +++ b/SysManager/SysManager/Views/PerformanceView.xaml @@ -61,15 +61,15 @@ + IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=balanced}"/> + IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=high}"/> + IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=ultimate}"/>