fix(ui): surface worker-offline failures when deleting a task
DeleteTaskAsync had no IsConnected guard, unlike every other worker-dependent command in DetailsIslandViewModel, so an offline delete was a silent no-op. WorkerClient.DeleteTaskAsync also only caught HubException, letting the InvalidOperationException thrown by an inactive hub connection escape into the unobserved command task and vanish. Gate DeleteTaskCommand behind CanDeleteTask (Task != null && IsConnected), re-evaluate it on connection-state changes, widen WorkerClient to catch the connection-inactive case too, and wrap the ViewModel's call in try/catch as a second line of defense against a race between the guard and the call.
This commit is contained in:
@@ -57,12 +57,16 @@ public class DetailsIslandDeleteTaskTests : IDisposable
|
||||
|
||||
private sealed class RecordingWorkerClient : StubWorkerClient
|
||||
{
|
||||
public override bool IsConnected => true;
|
||||
public override bool IsConnected { get; } = true;
|
||||
public (bool Ok, string? Error) Result { get; set; } = (true, null);
|
||||
public string? DeletedTaskId { get; private set; }
|
||||
public Exception? ThrowOnDelete { get; set; }
|
||||
|
||||
public RecordingWorkerClient(bool isConnected = true) => IsConnected = isConnected;
|
||||
|
||||
public override Task<(bool Ok, string? Error)> DeleteTaskAsync(string taskId)
|
||||
{
|
||||
if (ThrowOnDelete != null) throw ThrowOnDelete;
|
||||
DeletedTaskId = taskId;
|
||||
return Task.FromResult(Result);
|
||||
}
|
||||
@@ -114,4 +118,35 @@ public class DetailsIslandDeleteTaskTests : IDisposable
|
||||
Assert.False(deleteFromListCalled);
|
||||
Assert.False(closed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteTask_WhenWorkerOffline_CommandIsDisabled()
|
||||
{
|
||||
var worker = new RecordingWorkerClient(isConnected: false);
|
||||
var vm = BuildVm(worker);
|
||||
vm.Bind(new TaskRowViewModel { Id = "task-del-3", Status = TaskStatus.Idle });
|
||||
|
||||
Assert.False(vm.DeleteTaskCommand.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteTask_WhenWorkerThrowsInvalidOperationException_SurfacesErrorAndKeepsDetailOpen()
|
||||
{
|
||||
var worker = new RecordingWorkerClient { ThrowOnDelete = new InvalidOperationException("Connection is not active.") };
|
||||
var vm = BuildVm(worker);
|
||||
vm.Bind(new TaskRowViewModel { Id = "task-del-4", Status = TaskStatus.Idle });
|
||||
|
||||
var deleteFromListCalled = false;
|
||||
vm.DeleteFromList = _ => { deleteFromListCalled = true; return Task.CompletedTask; };
|
||||
var closed = false;
|
||||
vm.CloseDetail = () => closed = true;
|
||||
string? reportedError = null;
|
||||
vm.ShowErrorAsync = msg => { reportedError = msg; return Task.CompletedTask; };
|
||||
|
||||
await vm.DeleteTaskCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.Equal("Connection is not active.", reportedError);
|
||||
Assert.False(deleteFromListCalled);
|
||||
Assert.False(closed);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user