review(Client.UI): single notification when removing non-retained alarm row
Re-review at 7286d320. -013: AlarmsViewModel.OnAlarmEvent removal path no longer fires a
redundant Replace+Remove (one Remove now), preventing a DataGrid re-paint flash. -012: add
update/remove-path test coverage. + TDD.
This commit is contained in:
@@ -153,6 +153,101 @@ public class AlarmsViewModelTests
|
||||
_vm.Interval.ShouldBe(1000);
|
||||
}
|
||||
|
||||
// --- Alarm update and non-retain remove paths (Client.UI-012 / Client.UI-013) ---
|
||||
|
||||
/// <summary>
|
||||
/// Regression test for Client.UI-013 / Client.UI-012 — a second event for the same
|
||||
/// source+condition must update the existing row in place rather than adding a duplicate.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AlarmEvent_ExistingAlarm_UpdatesInPlace()
|
||||
{
|
||||
var first = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 500, "Temperature high",
|
||||
retain: true, activeState: true, ackedState: false, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(first);
|
||||
|
||||
var updated = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 750, "Temperature very high",
|
||||
retain: true, activeState: true, ackedState: false, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(updated);
|
||||
|
||||
_vm.AlarmEvents.Count.ShouldBe(1);
|
||||
_vm.AlarmEvents[0].Severity.ShouldBe((ushort)750);
|
||||
_vm.AlarmEvents[0].Message.ShouldBe("Temperature very high");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression test for Client.UI-013 — when an existing retained alarm becomes
|
||||
/// non-retained the row must be removed from the collection with exactly ONE collection
|
||||
/// mutation (not a Replace + Remove pair).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AlarmEvent_ExistingAlarmBecomesNonRetained_IsRemovedCleanly()
|
||||
{
|
||||
// Seed a retained alarm.
|
||||
var first = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 500, "Active",
|
||||
retain: true, activeState: true, ackedState: false, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(first);
|
||||
_vm.AlarmEvents.Count.ShouldBe(1);
|
||||
|
||||
// Track collection-change notifications.
|
||||
var changeCount = 0;
|
||||
_vm.AlarmEvents.CollectionChanged += (_, _) => changeCount++;
|
||||
|
||||
// Now the alarm is cleared (Retain = false).
|
||||
var cleared = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 500, "Cleared",
|
||||
retain: false, activeState: false, ackedState: false, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(cleared);
|
||||
|
||||
// Row must be removed.
|
||||
_vm.AlarmEvents.ShouldBeEmpty();
|
||||
// Exactly one collection-change notification (Remove), not two (Replace then Remove).
|
||||
changeCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a non-retained alarm that has no existing row in the collection
|
||||
/// is silently dropped (not added).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AlarmEvent_NonRetained_WithNoExistingRow_IsNotAdded()
|
||||
{
|
||||
var nonRetained = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 500, "Already cleared",
|
||||
retain: false, activeState: false, ackedState: false, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(nonRetained);
|
||||
|
||||
_vm.AlarmEvents.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that ActiveAlarmCount is updated correctly when an alarm is acknowledged
|
||||
/// via an event update (AckedState changes from false to true).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AlarmEvent_AcknowledgedUpdate_DecrementsActiveAlarmCount()
|
||||
{
|
||||
// Seed an unacknowledged active alarm.
|
||||
var active = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 500, "Active",
|
||||
retain: true, activeState: true, ackedState: false, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(active);
|
||||
_vm.ActiveAlarmCount.ShouldBe(1);
|
||||
|
||||
// Acknowledge the alarm.
|
||||
var acked = new AlarmEventArgs(
|
||||
"Source1", "HighAlarm", 500, "Acked",
|
||||
retain: true, activeState: true, ackedState: true, time: DateTime.UtcNow);
|
||||
_service.RaiseAlarmEvent(acked);
|
||||
|
||||
_vm.AlarmEvents.Count.ShouldBe(1);
|
||||
_vm.AlarmEvents[0].AckedState.ShouldBeTrue();
|
||||
_vm.ActiveAlarmCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression test for Client.UI-006 — when SubscribeAlarmsAsync throws, the failure must be
|
||||
/// surfaced to the operator via the view model's StatusMessage rather than silently swallowed.
|
||||
|
||||
Reference in New Issue
Block a user