using System; using System.Threading; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using Notification.Core; using Notification.Wpf.Controls; using Xunit; using ytLive.Services; namespace ytLive.Tests; /// /// TASK 24 integration: the real NotificationService against a real window-hosted /// ToastArea. Transient notifications expire on their own; errors stick until the /// area goes away. ONE integration test per the Good Dog Rule. /// [Collection("RealApp")] public class NotificationAreaIntegrationTests { private readonly RealAppHost _app; public NotificationAreaIntegrationTests(RealAppHost app) => _app = app; [Fact] public void Info_Auto_Dismisses_And_Error_Sticks() { Window? window = null; NotificationArea? area = null; NotificationService? service = null; _app.Run(() => { area = new NotificationArea { Name = NotificationService.AreaName, Position = NotificationPosition.BottomRight, MaxItems = 4 }; var grid = new Grid(); grid.Children.Add(area); window = new Window { Content = grid, Width = 400, Height = 300, WindowStartupLocation = WindowStartupLocation.Manual, Left = -32000, Top = 0, ShowInTaskbar = false, ShowActivated = false }; window.Show(); service = new NotificationService(); }); try { service!.Info("Hello", "transient"); Assert.True(WaitFor(() => ItemCount(area!) == 1), "info toast never appeared"); Assert.True(WaitFor(() => ItemCount(area!) == 0, TimeSpan.FromSeconds(8)), "info toast never auto-dismissed"); service!.Error("Broken", "sticky"); Assert.True(WaitFor(() => ItemCount(area!) == 1), "error toast never appeared"); Thread.Sleep(1200); Assert.Equal(1, ItemCount(area!)); } finally { _app.Run(() => window!.Close()); } } private int ItemCount(NotificationArea area) { var count = 0; _app.Run(() => count = CountItems(area)); return count; } private static int CountItems(NotificationArea area) { var count = 0; Walk(area, ref count); return count; static void Walk(DependencyObject node, ref int count) { if (node is Panel panel && panel.Name == "PART_Items") { count += panel.Children.Count; return; } for (var i = 0; i < VisualTreeHelper.GetChildrenCount(node); i++) Walk(VisualTreeHelper.GetChild(node, i), ref count); } } private static bool WaitFor(Func condition, TimeSpan? timeout = null) { var deadline = DateTime.UtcNow + (timeout ?? TimeSpan.FromSeconds(3)); while (DateTime.UtcNow < deadline) { if (condition()) return true; Thread.Sleep(50); } return condition(); } }