Initial commit: SpdUp Windows Gaming Booster v1.0.0
- .NET 8 WPF app with full MVVM architecture - One-click boost: CPU, RAM, GPU, network, services, timer resolution - Real-time system monitor (LibreHardwareMonitor) - Auto game detection (30+ games) - RAM optimizer with kernel-level cleanup - Network optimizer (TCP registry tweaks) - Service manager, process manager, shader cache cleaner - Desktop widget overlay - Debug log viewer - Custom dark gaming theme with trans pride colors - 84 unit tests (xUnit) - Inno Setup installer script - Landing page website
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using SpdUp.Converters;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for WPF value converters.
|
||||
/// </summary>
|
||||
public class ConverterTests
|
||||
{
|
||||
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
|
||||
|
||||
// ── BoolToVisibilityConverter ─────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void BoolToVisibility_True_ReturnsVisible()
|
||||
{
|
||||
var conv = new BoolToVisibilityConverter();
|
||||
var result = conv.Convert(true, typeof(Visibility), null!, Culture);
|
||||
Assert.Equal(Visibility.Visible, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolToVisibility_False_ReturnsCollapsed()
|
||||
{
|
||||
var conv = new BoolToVisibilityConverter();
|
||||
var result = conv.Convert(false, typeof(Visibility), null!, Culture);
|
||||
Assert.Equal(Visibility.Collapsed, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolToVisibility_ConvertBack_Visible_ReturnsTrue()
|
||||
{
|
||||
var conv = new BoolToVisibilityConverter();
|
||||
var result = conv.ConvertBack(Visibility.Visible, typeof(bool), null!, Culture);
|
||||
Assert.Equal(true, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolToVisibility_ConvertBack_Collapsed_ReturnsFalse()
|
||||
{
|
||||
var conv = new BoolToVisibilityConverter();
|
||||
var result = conv.ConvertBack(Visibility.Collapsed, typeof(bool), null!, Culture);
|
||||
Assert.Equal(false, result);
|
||||
}
|
||||
|
||||
// ── InverseBoolToVisibilityConverter ──────────────────────
|
||||
|
||||
[Fact]
|
||||
public void InverseBoolToVisibility_True_ReturnsCollapsed()
|
||||
{
|
||||
var conv = new InverseBoolToVisibilityConverter();
|
||||
var result = conv.Convert(true, typeof(Visibility), null!, Culture);
|
||||
Assert.Equal(Visibility.Collapsed, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InverseBoolToVisibility_False_ReturnsVisible()
|
||||
{
|
||||
var conv = new InverseBoolToVisibilityConverter();
|
||||
var result = conv.Convert(false, typeof(Visibility), null!, Culture);
|
||||
Assert.Equal(Visibility.Visible, result);
|
||||
}
|
||||
|
||||
// ── PercentToColorConverter ──────────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData(95f, 255, 69, 58)] // red for > 90
|
||||
[InlineData(91f, 255, 69, 58)] // red at boundary
|
||||
[InlineData(80f, 255, 159, 10)] // orange for > 70
|
||||
[InlineData(71f, 255, 159, 10)] // orange at boundary
|
||||
[InlineData(50f, 48, 209, 88)] // green for <= 70
|
||||
[InlineData(0f, 48, 209, 88)] // green at zero
|
||||
public void PercentToColor_ReturnsCorrectColor(float pct, byte r, byte g, byte b)
|
||||
{
|
||||
var conv = new PercentToColorConverter();
|
||||
var result = conv.Convert(pct, typeof(Brush), null!, Culture) as SolidColorBrush;
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(r, result.Color.R);
|
||||
Assert.Equal(g, result.Color.G);
|
||||
Assert.Equal(b, result.Color.B);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PercentToColor_ConvertBack_Throws()
|
||||
{
|
||||
var conv = new PercentToColorConverter();
|
||||
Assert.Throws<NotSupportedException>(() =>
|
||||
conv.ConvertBack(null!, typeof(float), null!, Culture));
|
||||
}
|
||||
|
||||
// ── NullToVisibilityConverter ────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void NullToVisibility_Null_ReturnsCollapsed()
|
||||
{
|
||||
var conv = new NullToVisibilityConverter();
|
||||
var result = conv.Convert(null!, typeof(Visibility), null!, Culture);
|
||||
Assert.Equal(Visibility.Collapsed, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullToVisibility_NotNull_ReturnsVisible()
|
||||
{
|
||||
var conv = new NullToVisibilityConverter();
|
||||
var result = conv.Convert("something", typeof(Visibility), null!, Culture);
|
||||
Assert.Equal(Visibility.Visible, result);
|
||||
}
|
||||
|
||||
// ── FloatFormatConverter ─────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void FloatFormat_DefaultFormat_OneDecimal()
|
||||
{
|
||||
var conv = new FloatFormatConverter();
|
||||
var result = (string)conv.Convert(42.567f, typeof(string), null!, CultureInfo.InvariantCulture);
|
||||
// Converter uses current culture internally, so parse back to float
|
||||
Assert.True(float.TryParse(result, System.Globalization.NumberStyles.Float, CultureInfo.CurrentCulture, out var parsed));
|
||||
Assert.Equal(42.6f, parsed, 0.05f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FloatFormat_CustomFormat()
|
||||
{
|
||||
var conv = new FloatFormatConverter();
|
||||
var result = (string)conv.Convert(42.567f, typeof(string), "F2", CultureInfo.InvariantCulture);
|
||||
Assert.True(float.TryParse(result, System.Globalization.NumberStyles.Float, CultureInfo.CurrentCulture, out var parsed));
|
||||
Assert.Equal(42.57f, parsed, 0.005f);
|
||||
}
|
||||
|
||||
// ── BoostButtonTextConverter ─────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void BoostButtonText_True_ReturnsStopText()
|
||||
{
|
||||
var conv = new BoostButtonTextConverter();
|
||||
var result = conv.Convert(true, typeof(string), null!, Culture);
|
||||
Assert.Contains("STOP", result as string ?? "");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoostButtonText_False_ReturnsBoostText()
|
||||
{
|
||||
var conv = new BoostButtonTextConverter();
|
||||
var result = conv.Convert(false, typeof(string), null!, Culture);
|
||||
Assert.Contains("BOOST", result as string ?? "");
|
||||
}
|
||||
|
||||
// ── BoostButtonColorConverter ────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void BoostButtonColor_True_ReturnsRed()
|
||||
{
|
||||
var conv = new BoostButtonColorConverter();
|
||||
var result = conv.Convert(true, typeof(Brush), null!, Culture) as SolidColorBrush;
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(255, result.Color.R);
|
||||
Assert.Equal(69, result.Color.G);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoostButtonColor_False_ReturnsGreen()
|
||||
{
|
||||
var conv = new BoostButtonColorConverter();
|
||||
var result = conv.Convert(false, typeof(Brush), null!, Culture) as SolidColorBrush;
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(48, result.Color.R);
|
||||
Assert.Equal(209, result.Color.G);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
@@ -0,0 +1,117 @@
|
||||
using SpdUp.Core;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the Logger class.
|
||||
/// </summary>
|
||||
public class LoggerTests
|
||||
{
|
||||
[Fact]
|
||||
public void Info_FiresLogWrittenEvent()
|
||||
{
|
||||
string? received = null;
|
||||
Logger.LogWritten += handler;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("test info message");
|
||||
Assert.NotNull(received);
|
||||
Assert.Contains("[INFO]", received);
|
||||
Assert.Contains("test info message", received);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.LogWritten -= handler;
|
||||
}
|
||||
|
||||
void handler(string line) => received = line;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Warn_FiresLogWrittenEventWithWarnLevel()
|
||||
{
|
||||
string? received = null;
|
||||
Logger.LogWritten += handler;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Warn("test warning");
|
||||
Assert.NotNull(received);
|
||||
Assert.Contains("[WARN]", received);
|
||||
Assert.Contains("test warning", received);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.LogWritten -= handler;
|
||||
}
|
||||
|
||||
void handler(string line) => received = line;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Error_IncludesExceptionDetails()
|
||||
{
|
||||
string? received = null;
|
||||
Logger.LogWritten += handler;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Error("something failed", new InvalidOperationException("bad state"));
|
||||
Assert.NotNull(received);
|
||||
Assert.Contains("[ERROR]", received);
|
||||
Assert.Contains("something failed", received);
|
||||
Assert.Contains("InvalidOperationException", received);
|
||||
Assert.Contains("bad state", received);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.LogWritten -= handler;
|
||||
}
|
||||
|
||||
void handler(string line) => received = line;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Error_WithoutException_DoesNotContainPipe()
|
||||
{
|
||||
string? received = null;
|
||||
Logger.LogWritten += handler;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Error("simple error");
|
||||
Assert.NotNull(received);
|
||||
Assert.Contains("[ERROR]", received);
|
||||
Assert.Contains("simple error", received);
|
||||
Assert.DoesNotContain("|", received);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.LogWritten -= handler;
|
||||
}
|
||||
|
||||
void handler(string line) => received = line;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LogWritten_IncludesTimestamp()
|
||||
{
|
||||
string? received = null;
|
||||
Logger.LogWritten += handler;
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("timestamp check");
|
||||
Assert.NotNull(received);
|
||||
// Format: [yyyy-MM-dd HH:mm:ss.fff]
|
||||
Assert.Matches(@"\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}\]", received);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.LogWritten -= handler;
|
||||
}
|
||||
|
||||
void handler(string line) => received = line;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
using SpdUp.Models;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the model classes.
|
||||
/// </summary>
|
||||
public class ModelTests
|
||||
{
|
||||
// ── SystemMetrics ────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void SystemMetrics_RamUsagePercent_CalculatesCorrectly()
|
||||
{
|
||||
var m = new SystemMetrics { RamUsageMB = 8000, RamTotalMB = 16000 };
|
||||
Assert.Equal(50f, m.RamUsagePercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SystemMetrics_RamUsagePercent_ZeroTotal_ReturnsZero()
|
||||
{
|
||||
var m = new SystemMetrics { RamUsageMB = 100, RamTotalMB = 0 };
|
||||
Assert.Equal(0f, m.RamUsagePercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SystemMetrics_RamUsagePercent_FullRam()
|
||||
{
|
||||
var m = new SystemMetrics { RamUsageMB = 16000, RamTotalMB = 16000 };
|
||||
Assert.Equal(100f, m.RamUsagePercent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SystemMetrics_DefaultTimestamp_IsNow()
|
||||
{
|
||||
var before = DateTime.Now.AddSeconds(-1);
|
||||
var m = new SystemMetrics();
|
||||
var after = DateTime.Now.AddSeconds(1);
|
||||
|
||||
Assert.InRange(m.Timestamp, before, after);
|
||||
}
|
||||
|
||||
// ── GameEntry ────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void GameEntry_ProcessNameFromPath_ExtractsFileName()
|
||||
{
|
||||
var result = GameEntry.ProcessNameFromPath(@"C:\Games\Cyberpunk2077\bin\Cyberpunk2077.exe");
|
||||
Assert.Equal("Cyberpunk2077", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameEntry_ProcessNameFromPath_HandlesForwardSlashes()
|
||||
{
|
||||
var result = GameEntry.ProcessNameFromPath("C:/Games/Game.exe");
|
||||
Assert.Equal("Game", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameEntry_ProcessNameFromPath_JustFileName()
|
||||
{
|
||||
var result = GameEntry.ProcessNameFromPath("myapp.exe");
|
||||
Assert.Equal("myapp", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameEntry_DefaultValues()
|
||||
{
|
||||
var g = new GameEntry();
|
||||
Assert.Equal(string.Empty, g.Name);
|
||||
Assert.Equal(string.Empty, g.ExecutablePath);
|
||||
Assert.Equal("Manual", g.LauncherSource);
|
||||
Assert.True(g.AutoBoost);
|
||||
Assert.Null(g.IconPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoostProfile_DefaultValues()
|
||||
{
|
||||
var p = new BoostProfile();
|
||||
Assert.True(p.CleanRam);
|
||||
Assert.True(p.SetHighPriority);
|
||||
Assert.True(p.ReduceTimerResolution);
|
||||
Assert.True(p.StopServices);
|
||||
Assert.False(p.OptimizeNetwork);
|
||||
}
|
||||
|
||||
// ── ProcessEntry ─────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ProcessEntry_Properties()
|
||||
{
|
||||
var p = new ProcessEntry
|
||||
{
|
||||
Pid = 1234,
|
||||
Name = "chrome",
|
||||
WindowTitle = "Google Chrome",
|
||||
CpuPercent = 12.5f,
|
||||
MemoryMB = 350f,
|
||||
PriorityClass = "High",
|
||||
IsSystem = false
|
||||
};
|
||||
|
||||
Assert.Equal(1234, p.Pid);
|
||||
Assert.Equal("chrome", p.Name);
|
||||
Assert.Equal("Google Chrome", p.WindowTitle);
|
||||
Assert.Equal(12.5f, p.CpuPercent);
|
||||
Assert.Equal(350f, p.MemoryMB);
|
||||
Assert.Equal("High", p.PriorityClass);
|
||||
Assert.False(p.IsSystem);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProcessEntry_DefaultPriority_IsNormal()
|
||||
{
|
||||
var p = new ProcessEntry { Pid = 1, Name = "test" };
|
||||
Assert.Equal("Normal", p.PriorityClass);
|
||||
}
|
||||
|
||||
// ── ServiceEntry ─────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ServiceEntry_DefaultStatus_IsUnknown()
|
||||
{
|
||||
var s = new ServiceEntry();
|
||||
Assert.Equal("Unknown", s.Status);
|
||||
Assert.False(s.WasRunningBeforeBoost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServiceEntry_Properties()
|
||||
{
|
||||
var s = new ServiceEntry
|
||||
{
|
||||
ServiceName = "WSearch",
|
||||
DisplayName = "Windows Search",
|
||||
Status = "Running",
|
||||
WasRunningBeforeBoost = true,
|
||||
Description = "Full-text search"
|
||||
};
|
||||
|
||||
Assert.Equal("WSearch", s.ServiceName);
|
||||
Assert.Equal("Windows Search", s.DisplayName);
|
||||
Assert.Equal("Running", s.Status);
|
||||
Assert.True(s.WasRunningBeforeBoost);
|
||||
}
|
||||
|
||||
// ── AppSettings ──────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void AppSettings_DefaultValues()
|
||||
{
|
||||
var s = new AppSettings();
|
||||
Assert.True(s.MinimizeToTray);
|
||||
Assert.False(s.StartWithWindows);
|
||||
Assert.True(s.AutoDetectGames);
|
||||
Assert.True(s.AutoBoostOnGameLaunch);
|
||||
Assert.False(s.ShowWidget);
|
||||
Assert.Equal("8.8.8.8", s.PingTarget);
|
||||
Assert.Equal("1.1.1.1", s.PreferredDns);
|
||||
Assert.Equal(1000, s.MonitoringIntervalMs);
|
||||
Assert.Contains("SysMain", s.ServicesToStop);
|
||||
Assert.Contains("WSearch", s.ServicesToStop);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AppSettings_WidgetDefaults()
|
||||
{
|
||||
var s = new AppSettings();
|
||||
Assert.True(s.WidgetCompactMode);
|
||||
Assert.Equal(100, s.WidgetLeft);
|
||||
Assert.Equal(100, s.WidgetTop);
|
||||
Assert.Equal(0.85, s.WidgetOpacity);
|
||||
Assert.Equal(1000, s.WidgetUpdateIntervalMs);
|
||||
Assert.False(s.WidgetClickThrough);
|
||||
}
|
||||
|
||||
// ── ShaderCleanResult ────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ShaderCleanResult_MBFreed_Calculation()
|
||||
{
|
||||
var r = new SpdUp.Services.ShaderCleanResult(10, 10_485_760); // 10 MB
|
||||
Assert.Equal(10.0, r.MBFreed, 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShaderCleanResult_ZeroBytes()
|
||||
{
|
||||
var r = new SpdUp.Services.ShaderCleanResult(0, 0);
|
||||
Assert.Equal(0.0, r.MBFreed);
|
||||
Assert.Equal(0, r.FilesDeleted);
|
||||
}
|
||||
|
||||
// ── RamCleanupResult ─────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void RamCleanupResult_RecordWithSyntax()
|
||||
{
|
||||
var r = new SpdUp.Services.RamCleanupResult(50, 5, 0);
|
||||
var r2 = r with { StandbyPurged = 1 };
|
||||
|
||||
Assert.Equal(50, r2.TrimmedCount);
|
||||
Assert.Equal(5, r2.FailedCount);
|
||||
Assert.Equal(1, r2.StandbyPurged);
|
||||
}
|
||||
|
||||
// ── BoostResult ──────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void BoostResult_Properties()
|
||||
{
|
||||
var r = new SpdUp.Services.BoostResult(true, "Boost activated");
|
||||
Assert.True(r.Success);
|
||||
Assert.Equal("Boost activated", r.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoostResult_Failure()
|
||||
{
|
||||
var r = new SpdUp.Services.BoostResult(false, "Error occurred");
|
||||
Assert.False(r.Success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System.ComponentModel;
|
||||
using SpdUp.Core;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for ObservableObject base class.
|
||||
/// </summary>
|
||||
public class ObservableObjectTests
|
||||
{
|
||||
private sealed class TestObservable : ObservableObject
|
||||
{
|
||||
private string _name = string.Empty;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => SetProperty(ref _name, value);
|
||||
}
|
||||
|
||||
private int _count;
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set => SetProperty(ref _count, value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_NewValue_RaisesPropertyChanged()
|
||||
{
|
||||
var obj = new TestObservable();
|
||||
string? changedProp = null;
|
||||
obj.PropertyChanged += (_, e) => changedProp = e.PropertyName;
|
||||
|
||||
obj.Name = "hello";
|
||||
|
||||
Assert.Equal("Name", changedProp);
|
||||
Assert.Equal("hello", obj.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_SameValue_DoesNotRaisePropertyChanged()
|
||||
{
|
||||
var obj = new TestObservable { Name = "hello" };
|
||||
bool raised = false;
|
||||
obj.PropertyChanged += (_, _) => raised = true;
|
||||
|
||||
obj.Name = "hello";
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_ReturnsTrue_WhenValueChanged()
|
||||
{
|
||||
var obj = new TestObservable();
|
||||
string? changedProp = null;
|
||||
obj.PropertyChanged += (_, e) => changedProp = e.PropertyName;
|
||||
|
||||
obj.Count = 42;
|
||||
|
||||
Assert.Equal("Count", changedProp);
|
||||
Assert.Equal(42, obj.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetProperty_ReturnsFalse_WhenValueUnchanged()
|
||||
{
|
||||
var obj = new TestObservable { Count = 5 };
|
||||
bool raised = false;
|
||||
obj.PropertyChanged += (_, _) => raised = true;
|
||||
|
||||
obj.Count = 5;
|
||||
|
||||
Assert.False(raised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleProperties_OnlyNotifyChanged()
|
||||
{
|
||||
var obj = new TestObservable();
|
||||
var notifications = new List<string>();
|
||||
obj.PropertyChanged += (_, e) => notifications.Add(e.PropertyName!);
|
||||
|
||||
obj.Name = "a";
|
||||
obj.Count = 1;
|
||||
obj.Name = "a"; // same – no notification
|
||||
obj.Count = 2;
|
||||
|
||||
Assert.Equal(3, notifications.Count);
|
||||
Assert.Equal(["Name", "Count", "Count"], notifications);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
using SpdUp.Core;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for RelayCommand and RelayCommand<T>.
|
||||
/// </summary>
|
||||
public class RelayCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void Execute_InvokesAction()
|
||||
{
|
||||
bool executed = false;
|
||||
var cmd = new RelayCommand(() => executed = true);
|
||||
|
||||
cmd.Execute(null);
|
||||
|
||||
Assert.True(executed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Execute_WithParameter_InvokesParameterizedAction()
|
||||
{
|
||||
object? received = null;
|
||||
var cmd = new RelayCommand(p => received = p);
|
||||
|
||||
cmd.Execute("test");
|
||||
|
||||
Assert.Equal("test", received);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_DefaultsToTrue()
|
||||
{
|
||||
var cmd = new RelayCommand(() => { });
|
||||
|
||||
Assert.True(cmd.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_RespectsCanExecuteFunc()
|
||||
{
|
||||
var cmd = new RelayCommand(() => { }, () => false);
|
||||
|
||||
Assert.False(cmd.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanExecute_ParameterizedPredicate()
|
||||
{
|
||||
var cmd = new RelayCommand(_ => { }, p => p is "go");
|
||||
|
||||
Assert.True(cmd.CanExecute("go"));
|
||||
Assert.False(cmd.CanExecute("stop"));
|
||||
Assert.False(cmd.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullAction()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new RelayCommand((Action<object?>)null!));
|
||||
}
|
||||
|
||||
// ── Generic RelayCommand<T> ──────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Generic_Execute_InvokesWithTypedParameter()
|
||||
{
|
||||
string? received = null;
|
||||
var cmd = new RelayCommand<string>(s => received = s);
|
||||
|
||||
cmd.Execute("hello");
|
||||
|
||||
Assert.Equal("hello", received);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generic_CanExecute_DefaultsToTrue()
|
||||
{
|
||||
var cmd = new RelayCommand<int>(_ => { });
|
||||
|
||||
Assert.True(cmd.CanExecute(42));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generic_CanExecute_RespectsFunc()
|
||||
{
|
||||
var cmd = new RelayCommand<int>(_ => { }, n => n > 0);
|
||||
|
||||
Assert.True(cmd.CanExecute(5));
|
||||
Assert.False(cmd.CanExecute(-1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generic_Constructor_ThrowsOnNullAction()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new RelayCommand<string>(null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using SpdUp.Models;
|
||||
using SpdUp.Services;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for services that can be tested without admin/system access.
|
||||
/// </summary>
|
||||
public class ServiceTests
|
||||
{
|
||||
// ── NetworkService.PingAsync ──────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task PingAsync_Localhost_ReturnsPositive()
|
||||
{
|
||||
var ms = await NetworkService.PingAsync("127.0.0.1", 2000);
|
||||
Assert.True(ms >= 0, $"Ping to localhost should succeed, got {ms}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PingAsync_InvalidHost_ReturnsNegative()
|
||||
{
|
||||
var ms = await NetworkService.PingAsync("0.0.0.0", 500);
|
||||
// 0.0.0.0 typically fails or returns -1
|
||||
// On some machines it may succeed — accept either
|
||||
Assert.True(ms >= -1);
|
||||
}
|
||||
|
||||
// ── GameDetectionService ─────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void GameDetectionService_InitialState_NoGameDetected()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
using var gds = new GameDetectionService(settings);
|
||||
|
||||
Assert.Null(gds.CurrentGameProcess);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameDetectionService_AddGameProcess()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
using var gds = new GameDetectionService(settings);
|
||||
|
||||
gds.AddGameProcess("TestGame");
|
||||
// AddGameProcess should not throw
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameDetectionService_StartAndStop_DoesNotThrow()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
using var gds = new GameDetectionService(settings);
|
||||
|
||||
gds.Start(60000); // very long interval so it doesn't actually poll during test
|
||||
gds.Stop();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GameDetectionService_Dispose_DoesNotThrow()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
var gds = new GameDetectionService(settings);
|
||||
gds.Dispose();
|
||||
// Double dispose should not throw
|
||||
gds.Dispose();
|
||||
}
|
||||
|
||||
// ── OptimizationService ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void OptimizationService_InitialState()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
var svc = new OptimizationService(settings);
|
||||
|
||||
Assert.False(svc.IsBoosted);
|
||||
Assert.Null(svc.BoostedProcessName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OptimizationService_Events_CanSubscribe()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
var svc = new OptimizationService(settings);
|
||||
|
||||
bool activated = false;
|
||||
bool deactivated = false;
|
||||
svc.BoostActivated += _ => activated = true;
|
||||
svc.BoostDeactivated += () => deactivated = true;
|
||||
|
||||
// Just verify event subscription doesn't throw
|
||||
Assert.False(activated);
|
||||
Assert.False(deactivated);
|
||||
}
|
||||
|
||||
// ── ServiceManagementService ─────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void KnownNonCriticalServices_ContainsExpectedEntries()
|
||||
{
|
||||
var known = ServiceManagementService.KnownNonCriticalServices;
|
||||
|
||||
Assert.True(known.ContainsKey("SysMain"));
|
||||
Assert.True(known.ContainsKey("WSearch"));
|
||||
Assert.True(known.ContainsKey("DiagTrack"));
|
||||
Assert.True(known.ContainsKey("XblAuthManager"));
|
||||
Assert.True(known.Count >= 10, "Should have at least 10 known services");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceList_NonExistentService_ReturnsNotFound()
|
||||
{
|
||||
var entries = ServiceManagementService.GetServiceList(new[] { "SpdUpFakeService12345" });
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("NotFound", entries[0].Status);
|
||||
Assert.Equal("SpdUpFakeService12345", entries[0].ServiceName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceList_EmptyInput_ReturnsEmpty()
|
||||
{
|
||||
var entries = ServiceManagementService.GetServiceList(Array.Empty<string>());
|
||||
Assert.Empty(entries);
|
||||
}
|
||||
|
||||
// ── ShaderCleanResult ────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void ShaderCleanResult_MBFreed_LargeValue()
|
||||
{
|
||||
var r = new ShaderCleanResult(100, 1_073_741_824); // 1 GB
|
||||
Assert.Equal(1024.0, r.MBFreed, 0.1);
|
||||
}
|
||||
|
||||
// ── RamOptimizationService ───────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void RamOptimizationService_GetMemoryStatus_ReturnsValidValues()
|
||||
{
|
||||
var (totalMB, availMB, loadPercent) = RamOptimizationService.GetMemoryStatus();
|
||||
|
||||
Assert.True(totalMB > 0, "Total RAM should be > 0");
|
||||
Assert.True(availMB > 0, "Available RAM should be > 0");
|
||||
Assert.True(availMB <= totalMB, "Available should be <= total");
|
||||
Assert.InRange(loadPercent, (uint)1, (uint)100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SpdUp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.Collections.Specialized;
|
||||
using SpdUp.Core;
|
||||
using SpdUp.ViewModels;
|
||||
|
||||
namespace SpdUp.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for DebugLogViewModel.
|
||||
/// Uses a helper to run on STA thread for WPF Dispatcher compatibility.
|
||||
/// </summary>
|
||||
public class DebugLogViewModelTests
|
||||
{
|
||||
private static void RunOnSta(Action action)
|
||||
{
|
||||
Exception? caught = null;
|
||||
var thread = new Thread(() =>
|
||||
{
|
||||
try { action(); }
|
||||
catch (Exception ex) { caught = ex; }
|
||||
});
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
if (caught is not null)
|
||||
throw new Xunit.Sdk.XunitException($"STA test failed: {caught.Message}", caught);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Initial_LogLines_IsEmpty()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
Assert.Empty(vm.LogLines);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AutoScroll_DefaultTrue()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
Assert.True(vm.AutoScroll);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AutoScroll_Settable()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
vm.AutoScroll = false;
|
||||
Assert.False(vm.AutoScroll);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterText_DefaultEmpty()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
Assert.Equal(string.Empty, vm.FilterText);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilterText_RaisesPropertyChanged()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
var changed = new List<string>();
|
||||
vm.PropertyChanged += (_, e) => changed.Add(e.PropertyName!);
|
||||
|
||||
vm.FilterText = "error";
|
||||
|
||||
Assert.Contains("FilterText", changed);
|
||||
Assert.Contains("FilteredLogLines", changed);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearCommand_ClearsLogLines()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
vm.LogLines.Add("test line 1");
|
||||
vm.LogLines.Add("test line 2");
|
||||
|
||||
vm.ClearCommand.Execute(null);
|
||||
|
||||
Assert.Empty(vm.LogLines);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilteredLogLines_NoFilter_ReturnsAll()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
vm.LogLines.Add("[INFO] started");
|
||||
vm.LogLines.Add("[ERROR] failed");
|
||||
vm.LogLines.Add("[WARN] low memory");
|
||||
|
||||
var filtered = vm.FilteredLogLines.ToList();
|
||||
Assert.Equal(3, filtered.Count);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilteredLogLines_WithFilter_ReturnsMatches()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
vm.LogLines.Add("[INFO] started");
|
||||
vm.LogLines.Add("[ERROR] something failed");
|
||||
vm.LogLines.Add("[WARN] low memory");
|
||||
|
||||
vm.FilterText = "error";
|
||||
|
||||
var filtered = vm.FilteredLogLines.ToList();
|
||||
Assert.Single(filtered);
|
||||
Assert.Contains("ERROR", filtered[0]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FilteredLogLines_CaseInsensitive()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
vm.LogLines.Add("[INFO] Test Message");
|
||||
|
||||
vm.FilterText = "test message";
|
||||
|
||||
Assert.Single(vm.FilteredLogLines);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Commands_AreNotNull()
|
||||
{
|
||||
RunOnSta(() =>
|
||||
{
|
||||
var vm = new DebugLogViewModel();
|
||||
Assert.NotNull(vm.ClearCommand);
|
||||
Assert.NotNull(vm.CopyAllCommand);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user