diff --git a/SysManager/SysManager.Tests/AppPackageTests.cs b/SysManager/SysManager.Tests/AppPackageTests.cs
index 47ac18d3..57fe7009 100644
--- a/SysManager/SysManager.Tests/AppPackageTests.cs
+++ b/SysManager/SysManager.Tests/AppPackageTests.cs
@@ -32,16 +32,9 @@ public void IsSelected_RaisesPropertyChanged()
Assert.Contains(nameof(AppPackage.IsSelected), raised);
}
- [Fact]
- public void Status_Transitions_ArePossible()
- {
- var p = new AppPackage();
- foreach (var s in new[] { "Pending", "Upgrading...", "Done", "Failed (exit 1)" })
- {
- p.Status = s;
- Assert.Equal(s, p.Status);
- }
- }
+ // Status_Transitions_ArePossible was removed: assigning four strings to a string property and
+ // reading each back cannot fail. "Transitions are possible" was never in question — what would
+ // be worth pinning is who WRITES those values, which belongs to the winget service's tests.
[Fact]
public void ScenarioLikeRealData_IsStored()
diff --git a/SysManager/SysManager.Tests/CleanupViewModelTests.cs b/SysManager/SysManager.Tests/CleanupViewModelTests.cs
index 49dd9a94..cca600d0 100644
--- a/SysManager/SysManager.Tests/CleanupViewModelTests.cs
+++ b/SysManager/SysManager.Tests/CleanupViewModelTests.cs
@@ -330,16 +330,9 @@ public void StatusMessage_Setter_RaisesPropertyChanged()
Assert.Equal("hello", vm.StatusMessage);
}
- [Fact]
- public void Progress_AcceptsFullRange()
- {
- var vm = NewVm();
- foreach (var p in new[] { 0, 1, 50, 99, 100 })
- {
- vm.Progress = p;
- Assert.Equal(p, vm.Progress);
- }
- }
+ // Progress_AcceptsFullRange was removed: it looped five values through a bare
+ // [ObservableProperty] and read each back. The name implied a 0-100 contract that nothing
+ // enforces (ViewModelBase._progress is unclamped; the ProgressBar clamps visually).
[Fact]
public void IsProgressIndeterminate_TogglesCleanly()
@@ -386,22 +379,9 @@ public async Task PreScan_EventuallyPopulatesLabels()
Assert.NotEqual("Scanning…", vm.RecycleBinLabel);
}
- [Fact]
- public void TempSizeLabel_CanBeSetDirectly()
- {
- var vm = NewVm();
- vm.TempSizeLabel = "42.0 MB can be freed";
- Assert.Equal("42.0 MB can be freed", vm.TempSizeLabel);
- }
-
- [Fact]
- public void RecycleBinLabel_CanBeSetDirectly()
- {
- var vm = NewVm();
- vm.RecycleBinLabel = "Empty";
- Assert.Equal("Empty", vm.RecycleBinLabel);
- }
-
+ // A "…_CanBeSetDirectly" round-trip was removed here: it set a bare [ObservableProperty]
+ // and read it straight back, so only the source generator could fail it. The labels' real
+ // behaviour is covered by …_DefaultIsScanning and PreScan_EventuallyPopulatesLabels above.
// ---------- progress feedback (regression) ----------
// CleanupView.xaml binds a progress bar to IsBusy and the sidebar spinner reads the same flag,
// but this VM never assigned it — so nothing appeared while SFC or DISM ran, which is minutes of
diff --git a/SysManager/SysManager.Tests/DashboardViewModelTests.cs b/SysManager/SysManager.Tests/DashboardViewModelTests.cs
index 0768ceaf..1b98f481 100644
--- a/SysManager/SysManager.Tests/DashboardViewModelTests.cs
+++ b/SysManager/SysManager.Tests/DashboardViewModelTests.cs
@@ -89,47 +89,27 @@ public void Command_IsExposedAndNotNull(string name)
}
// ---------- property setters ----------
-
- [Fact]
- public void OsLine_Setter_Works()
- {
- var vm = NewVm();
- vm.OsLine = "Windows 11 Pro";
- Assert.Equal("Windows 11 Pro", vm.OsLine);
- }
-
- [Fact]
- public void UptimeLine_Setter_Works()
- {
- var vm = NewVm();
- vm.UptimeLine = "Uptime 3d 5h";
- Assert.Equal("Uptime 3d 5h", vm.UptimeLine);
- }
-
- [Fact]
- public void CpuPercent_Setter_Works()
- {
- var vm = NewVm();
- vm.CpuPercent = 42.5;
- Assert.Equal(42.5, vm.CpuPercent);
- }
-
- [Fact]
- public void RamPercent_Setter_Works()
- {
- var vm = NewVm();
- vm.RamPercent = 67.3;
- Assert.Equal(67.3, vm.RamPercent);
- }
+ // The four "…_Setter_Works" round-trips that lived here were removed: each set a bare
+ // [ObservableProperty] and read it straight back, which can only fail if the CommunityToolkit
+ // source generator breaks. What a binding depends on — the change notification — is covered by
+ // Setter_FiresPropertyChanged below, which the two percentages were added to.
// ---------- PropertyChanged ----------
+ ///
+ /// Every bound property must raise PropertyChanged: the dashboard is written by a background
+ /// poll loop, so the UI only updates on the notification. The parameter is object so the two
+ /// percentages can join the string rows — they arrived here when their standalone round-trip tests
+ /// were removed, because notification is the half that a binding actually depends on.
+ ///
[Theory]
[InlineData(nameof(DashboardViewModel.OsLine), "test")]
[InlineData(nameof(DashboardViewModel.UptimeLine), "test")]
[InlineData(nameof(DashboardViewModel.CpuName), "test")]
[InlineData(nameof(DashboardViewModel.GpuName), "test")]
- public void Setter_FiresPropertyChanged(string propName, string value)
+ [InlineData(nameof(DashboardViewModel.CpuPercent), 42.5)]
+ [InlineData(nameof(DashboardViewModel.RamPercent), 67.3)]
+ public void Setter_FiresPropertyChanged(string propName, object value)
{
var vm = NewVm();
var fired = false;
diff --git a/SysManager/SysManager.Tests/DuplicateFileViewModelTests.cs b/SysManager/SysManager.Tests/DuplicateFileViewModelTests.cs
index 2a093de8..4f25de52 100644
--- a/SysManager/SysManager.Tests/DuplicateFileViewModelTests.cs
+++ b/SysManager/SysManager.Tests/DuplicateFileViewModelTests.cs
@@ -106,13 +106,10 @@ public void BrowseFolderCommand_Exists()
Assert.NotNull(vm.BrowseFolderCommand);
}
- [Fact]
- public void MinSizeKb_CanBeChanged()
- {
- var vm = NewVm();
- vm.MinSizeKb = 500;
- Assert.Equal(500, vm.MinSizeKb);
- }
+ // MinSizeKb_CanBeChanged was removed as a setter round-trip. The property's real consequence is
+ // `var minBytes = MinSizeKb * 1024` in ScanAsync, and asserting THAT needs a guard the code does
+ // not have yet (a large typed value overflows long and inverts the filter) — tracked separately
+ // so this test-only change stays behaviour-neutral.
[Fact]
public void SelectedFolder_CanBeChanged()
diff --git a/SysManager/SysManager.Tests/FriendlyEventEntryExtendedTests.cs b/SysManager/SysManager.Tests/FriendlyEventEntryExtendedTests.cs
index 8d3b33f8..a0caafe6 100644
--- a/SysManager/SysManager.Tests/FriendlyEventEntryExtendedTests.cs
+++ b/SysManager/SysManager.Tests/FriendlyEventEntryExtendedTests.cs
@@ -67,13 +67,32 @@ public void SeverityColor_UnknownSeverity_GivesFallback()
Assert.Equal(StatusColors.Neutral, e.SeverityColor);
}
+ ///
+ /// The extreme timestamps a real event log can hand us must not produce nonsense in the two strings
+ /// the user actually sees.
+ /// Replaces an assertion that stored DateTime.MinValue/MaxValue and read them back
+ /// — a round-trip through a generated setter, proving only that a DateTime field holds a
+ /// DateTime. The reachable question is what RelativeTime and FullTimestamp DO with
+ /// those values, since both are computed from Timestamp and RelativeTime subtracts it
+ /// from DateTime.Now.
+ /// This pins the behaviour rather than changing it: MinValue is special-cased to an em dash, and
+ /// a FUTURE stamp (a clock correction, or a log copied from a machine running ahead) yields "just now",
+ /// because the negative span falls into the first branch. Imprecise but harmless — and far better than
+ /// printing "in -3d". Asserted so a future reordering of those branches cannot silently start showing
+ /// the user a negative duration.
+ ///
[Fact]
- public void TimestampsVastlyDifferent_StillStorable()
+ public void ExtremeTimestamps_ProduceSaneDisplayStrings()
{
- var e = new FriendlyEventEntry { Timestamp = DateTime.MinValue };
- Assert.Equal(DateTime.MinValue, e.Timestamp);
- e.Timestamp = DateTime.MaxValue;
- Assert.Equal(DateTime.MaxValue, e.Timestamp);
+ var min = new FriendlyEventEntry { Timestamp = DateTime.MinValue };
+ Assert.Equal("—", min.RelativeTime);
+ Assert.Equal("0001-01-01 00:00:00", min.FullTimestamp);
+
+ var future = new FriendlyEventEntry { Timestamp = DateTime.MaxValue };
+ Assert.Equal("just now", future.RelativeTime);
+ Assert.Equal("9999-12-31 23:59:59", future.FullTimestamp);
+ // The point of the assertion: no negative duration ever reaches the user.
+ Assert.DoesNotContain("-", future.RelativeTime);
}
[Fact]
diff --git a/SysManager/SysManager.Tests/PerformanceViewModelTests.cs b/SysManager/SysManager.Tests/PerformanceViewModelTests.cs
index bf2b7959..c37adf02 100644
--- a/SysManager/SysManager.Tests/PerformanceViewModelTests.cs
+++ b/SysManager/SysManager.Tests/PerformanceViewModelTests.cs
@@ -123,13 +123,8 @@ public void Constructor_WantToggles_DefaultFalse()
// ── Property changes ──
- [Fact]
- public void SelectedPlan_CanBeChanged()
- {
- var vm = NewVm();
- vm.SelectedPlan = "ultimate";
- Assert.Equal("ultimate", vm.SelectedPlan);
- }
+ // SelectedPlan_CanBeChanged was removed as a pure setter round-trip: the default is pinned by
+ // Constructor_SelectedPlan_DefaultBalanced and the notification by SelectedPlan_NotifiesPropertyChanged.
[Fact]
public void WantVisualEffectsReduced_CanBeToggled()
diff --git a/SysManager/SysManager.Tests/PingTargetTests.cs b/SysManager/SysManager.Tests/PingTargetTests.cs
index 41f2320f..1a7378be 100644
--- a/SysManager/SysManager.Tests/PingTargetTests.cs
+++ b/SysManager/SysManager.Tests/PingTargetTests.cs
@@ -90,15 +90,7 @@ public void Role_CanBeSet(TargetRole role)
Assert.Equal(role, t.Role);
}
- [Fact]
- public void Stats_CanBeUpdated()
- {
- var t = new PingTarget();
- t.AverageMs = 25.3;
- t.JitterMs = 2.1;
- t.LossPercent = 5.0;
- Assert.Equal(25.3, t.AverageMs);
- Assert.Equal(2.1, t.JitterMs);
- Assert.Equal(5.0, t.LossPercent);
- }
+ // Stats_CanBeUpdated was removed: three doubles assigned and read back on a model with no
+ // computed members over them. PingTarget's meaningful behaviour is in the ping service, which
+ // has its own tests.
}
diff --git a/SysManager/SysManager.Tests/ProcessManagerViewModelTests.cs b/SysManager/SysManager.Tests/ProcessManagerViewModelTests.cs
index f08504c5..354d3f42 100644
--- a/SysManager/SysManager.Tests/ProcessManagerViewModelTests.cs
+++ b/SysManager/SysManager.Tests/ProcessManagerViewModelTests.cs
@@ -43,12 +43,50 @@ public void FilterText_DefaultEmpty()
Assert.Equal("", vm.FilterText);
}
+ ///
+ /// Typing in the search box must actually narrow the bound list.
+ /// Replaces an assertion that set FilterText and then read FilterText back — a
+ /// round-trip through a generated [ObservableProperty] setter, which can only fail if the
+ /// source generator itself breaks. What the user depends on is the CONSEQUENCE:
+ /// OnFilterTextChanged calls ApplyFilter, which rebuilds FilteredProcesses —
+ /// the collection the DataGrid binds to. A filter wired to nothing is a defect this project has
+ /// shipped before (batch 30, five unreachable Services filters), and the old assertion could not
+ /// have caught it.
+ ///
[Fact]
- public void FilterText_CanBeChanged()
+ public void FilterText_NarrowsTheBoundList()
{
var vm = new ProcessManagerViewModel(new Services.ProcessManagerService());
+ vm.Processes.Clear();
+ vm.Processes.Add(new ProcessEntry { Pid = 1, Name = "chrome", MemoryBytes = 300 });
+ vm.Processes.Add(new ProcessEntry { Pid = 2, Name = "notepad", MemoryBytes = 200 });
+
vm.FilterText = "chrome";
- Assert.Equal("chrome", vm.FilterText);
+
+ Assert.Equal(["chrome"], vm.FilteredProcesses.Select(p => p.Name).ToArray());
+
+ // …and clearing it restores the full list, so the filter is not one-way.
+ vm.FilterText = "";
+ Assert.Equal(2, vm.FilteredProcesses.Count);
+ }
+
+ ///
+ /// The filter matches a PID and the description fields too, not only the name: three separate
+ /// Matches… helpers feed it and only the name path was ever exercised.
+ ///
+ [Fact]
+ public void FilterText_AlsoMatchesPidAndDescription()
+ {
+ var vm = new ProcessManagerViewModel(new Services.ProcessManagerService());
+ vm.Processes.Clear();
+ vm.Processes.Add(new ProcessEntry { Pid = 4321, Name = "svchost", PlainDescription = "Windows service host" });
+ vm.Processes.Add(new ProcessEntry { Pid = 9, Name = "notepad" });
+
+ vm.FilterText = "4321";
+ Assert.Equal(["svchost"], vm.FilteredProcesses.Select(p => p.Name).ToArray());
+
+ vm.FilterText = "service host";
+ Assert.Equal(["svchost"], vm.FilteredProcesses.Select(p => p.Name).ToArray());
}
[Fact]
diff --git a/SysManager/SysManager.Tests/UninstallerViewModelTests.cs b/SysManager/SysManager.Tests/UninstallerViewModelTests.cs
index 074adfeb..d24537c5 100644
--- a/SysManager/SysManager.Tests/UninstallerViewModelTests.cs
+++ b/SysManager/SysManager.Tests/UninstallerViewModelTests.cs
@@ -56,12 +56,46 @@ public void Summary_HasDefaultValue()
Assert.False(string.IsNullOrEmpty(vm.Summary));
}
+ ///
+ /// Typing in the search box must narrow the bound list AND correct the count and summary the user
+ /// reads above it. Replaces a FilterText round-trip that only exercised the generated setter;
+ /// ApplyFilter rebuilds FilteredApps, recomputes AppCount and rewrites
+ /// Summary, and none of those three was asserted anywhere.
+ ///
[Fact]
- public void FilterText_CanBeChanged()
+ public void FilterText_NarrowsTheListAndCorrectsTheCountAndSummary()
{
var vm = NewVm();
+ vm.AllApps.Clear();
+ vm.AllApps.Add(new InstalledApp { Name = "Google Chrome", Id = "Google.Chrome" });
+ vm.AllApps.Add(new InstalledApp { Name = "Notepad++", Id = "Notepad.Plus" });
+
vm.FilterText = "chrome";
- Assert.Equal("chrome", vm.FilterText);
+
+ Assert.Equal(["Google Chrome"], vm.FilteredApps.Select(a => a.Name).ToArray());
+ Assert.Equal(1, vm.AppCount);
+ Assert.Contains("of 2 total", vm.Summary);
+
+ // Clearing it restores everything, and the summary drops the "(of N total)" qualifier because
+ // nothing is filtered out any more.
+ vm.FilterText = "";
+ Assert.Equal(2, vm.FilteredApps.Count);
+ Assert.Equal(2, vm.AppCount);
+ Assert.DoesNotContain("of 2 total", vm.Summary);
+ }
+
+ /// The filter matches the package Id too, not just the display name.
+ [Fact]
+ public void FilterText_AlsoMatchesThePackageId()
+ {
+ var vm = NewVm();
+ vm.AllApps.Clear();
+ vm.AllApps.Add(new InstalledApp { Name = "Google Chrome", Id = "Google.Chrome" });
+ vm.AllApps.Add(new InstalledApp { Name = "Notepad++", Id = "Notepad.Plus" });
+
+ vm.FilterText = "Notepad.Plus";
+
+ Assert.Equal(["Notepad++"], vm.FilteredApps.Select(a => a.Name).ToArray());
}
[Fact]