using System.Security.Claims; using ZB.MOM.WW.ScadaBridge.Security; using Bunit; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared; using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using NotificationListsPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Notifications.NotificationLists; namespace ZB.MOM.WW.ScadaBridge.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(JwtTokenService.UsernameClaimType, "tester"), new Claim(JwtTokenService.RoleClaimType, "Designer"), }; 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); } }