refactor(central-ui): contextual errors, parallel recipient load, delete-path test for Notification Lists

This commit is contained in:
Joseph Doherty
2026-05-19 05:54:48 -04:00
parent 0f90c0ad9c
commit 0fa4ac5525
2 changed files with 45 additions and 7 deletions

View File

@@ -104,14 +104,18 @@
{ {
_lists = (await NotificationRepository.GetAllNotificationListsAsync()).ToList(); _lists = (await NotificationRepository.GetAllNotificationListsAsync()).ToList();
_recipients.Clear(); _recipients.Clear();
foreach (var list in _lists) var recipientTasks = _lists.ToDictionary(
list => list.Id,
list => NotificationRepository.GetRecipientsByListIdAsync(list.Id));
await Task.WhenAll(recipientTasks.Values);
foreach (var (id, task) in recipientTasks)
{ {
_recipients[list.Id] = await NotificationRepository.GetRecipientsByListIdAsync(list.Id); _recipients[id] = task.Result;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_errorMessage = ex.Message; _errorMessage = $"Failed to load notification lists: {ex.Message}";
} }
_loading = false; _loading = false;
} }
@@ -131,7 +135,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
_toast.ShowError(ex.Message); _toast.ShowError($"Failed to delete notification list: {ex.Message}");
} }
} }
} }

View File

@@ -44,8 +44,11 @@ public class NotificationListsPageTests : BunitContext
var cut = Render<NotificationListsPage>(); var cut = Render<NotificationListsPage>();
Assert.Contains("Ops On-Call", cut.Markup); cut.WaitForAssertion(() =>
Assert.Contains("jane@example.com", cut.Markup); {
Assert.Contains("Ops On-Call", cut.Markup);
Assert.Contains("jane@example.com", cut.Markup);
});
} }
[Fact] [Fact]
@@ -60,7 +63,38 @@ public class NotificationListsPageTests : BunitContext
var cut = Render<NotificationListsPage>(); var cut = Render<NotificationListsPage>();
Assert.Contains("No notification lists", cut.Markup); cut.WaitForAssertion(() =>
Assert.Contains("No notification lists", cut.Markup));
}
[Fact]
public void DeleteList_ConfirmsThenDeletesAndReloads()
{
var repo = Substitute.For<INotificationRepository>();
repo.GetAllNotificationListsAsync()
.Returns(Task.FromResult<IReadOnlyList<NotificationList>>(
new List<NotificationList> { new("Ops On-Call") { Id = 1 } }));
repo.GetRecipientsByListIdAsync(1)
.Returns(Task.FromResult<IReadOnlyList<NotificationRecipient>>(
new List<NotificationRecipient>()));
Services.AddSingleton(repo);
WireAuthAndDialog();
var cut = Render<NotificationListsPage>();
cut.WaitForState(() => cut.Markup.Contains("Ops On-Call"));
var deleteButton = cut.FindAll("tbody tr button")
.First(b => b.TextContent.Contains("Delete"));
deleteButton.Click();
cut.WaitForAssertion(() =>
{
repo.Received().DeleteNotificationListAsync(1);
repo.Received().SaveChangesAsync();
// Reload re-invokes the list query (once on init, once after delete).
repo.Received(2).GetAllNotificationListsAsync();
});
} }
/// <summary>A dialog service that auto-confirms, so action paths run end-to-end.</summary> /// <summary>A dialog service that auto-confirms, so action paths run end-to-end.</summary>