using System.Security.Claims; using Bunit; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using ScadaLink.CentralUI.Components.Shared; using ScadaLink.Commons.Entities.Notifications; using ScadaLink.Commons.Interfaces.Repositories; using NotificationListsPage = ScadaLink.CentralUI.Components.Pages.Notifications.NotificationLists; namespace ScadaLink.CentralUI.Tests.Pages; /// /// bUnit rendering tests for the standalone Notification Lists page (Task 7). /// public class NotificationListsPageTests : BunitContext { private void WireAuthAndDialog() { Services.AddSingleton(new AlwaysConfirmDialogService()); var claims = new[] { new Claim("Username", "tester"), new Claim(ClaimTypes.Role, "Design"), }; var user = new ClaimsPrincipal(new ClaimsIdentity(claims, "TestAuth")); Services.AddSingleton(new TestAuthStateProvider(user)); Services.AddAuthorizationCore(); } [Fact] public void RendersNotificationListRows() { var repo = Substitute.For(); repo.GetAllNotificationListsAsync() .Returns(Task.FromResult>( new List { new("Ops On-Call") { Id = 1 } })); repo.GetRecipientsByListIdAsync(1) .Returns(Task.FromResult>( new List { new("Jane", "jane@example.com") })); Services.AddSingleton(repo); WireAuthAndDialog(); var cut = Render(); cut.WaitForAssertion(() => { Assert.Contains("Ops On-Call", cut.Markup); Assert.Contains("jane@example.com", cut.Markup); }); } [Fact] public void ShowsEmptyState_WhenNoLists() { var repo = Substitute.For(); repo.GetAllNotificationListsAsync() .Returns(Task.FromResult>( new List())); Services.AddSingleton(repo); WireAuthAndDialog(); var cut = Render(); cut.WaitForAssertion(() => Assert.Contains("No notification lists", cut.Markup)); } [Fact] public void DeleteList_ConfirmsThenDeletesAndReloads() { var repo = Substitute.For(); repo.GetAllNotificationListsAsync() .Returns(Task.FromResult>( new List { new("Ops On-Call") { Id = 1 } })); repo.GetRecipientsByListIdAsync(1) .Returns(Task.FromResult>( new List())); Services.AddSingleton(repo); WireAuthAndDialog(); var cut = Render(); 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(); }); } /// A dialog service that auto-confirms, so action paths run end-to-end. private sealed class AlwaysConfirmDialogService : IDialogService { public Task ConfirmAsync(string title, string message, bool danger = false) => Task.FromResult(true); public Task PromptAsync( string title, string label, string initialValue = "", string? placeholder = null) => Task.FromResult(null); } }