using System; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Effects; using Microsoft.Data.Sqlite; using Xunit; using Xunit.Sdk; using ytLive.Controls; using ytLive.Models; using ytLive.Services; using ytLive.ViewModels; namespace ytLive.Tests; /// The hole that let two regressions reach the creator on 2026-10-30: the Socials drawer's /// appearance controls were tested against the RENDERER, while the preview is its own XAML tree — /// so a control could paint nothing, or hide the bar entirely, with every test green. /// /// This drives the real drawer on a real MainWindow and reads the real preview elements: /// the bar stays visible when the plate is off, the text colour reaches the glyphs, the plate /// carries its colour and opacity, the shadow switches on, and Cancel restores the pre-edit /// look. [Collection("RealApp")] public class SocialsPreviewTests { private readonly RealAppHost _app; public SocialsPreviewTests(RealAppHost app) => _app = app; [Fact] public void The_preview_bar_follows_the_drawer_controls() => _app.Run(Run); private static void Run() { var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-socialspreview-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = tempDb; MainWindow? window = null; try { var seeded = new SocialsConfig { BarEnabled = true }; seeded.Entries.Add(new SocialEntry { Service = SocialService.YouTube, Handle = "creator", ProfileUrl = "https://youtube.com/@creator", }); using (var store = new LayoutStore(tempDb)) store.Save(Array.Empty(), null, seeded); SqliteConnection.ClearAllPools(); window = new MainWindow(); var vm = (MainViewModel)window.DataContext!; window.Show(); window.UpdateLayout(); var preview = (PreviewPane)window.FindName("PreviewPane")!; var bar = (FrameworkElement)preview.FindName("SocialBarElement")!; var plate = FindChild(bar)!; var label = FindChild(bar)!; Assert.Equal(Visibility.Visible, bar.Visibility); // 1. Drawer edits land on the preview live — no Save needed. vm.OpenSocialDialogCommand.Execute(null); var drawer = vm.SocialsDrawer!; drawer.BarTextColor = "#FF0000"; window.UpdateLayout(); Assert.Equal(Colors.Red, ((SolidColorBrush)label.Foreground).Color); // 2. Plate off (the default) = transparent plate, NOT a hidden bar. Assert.Equal(0, ((SolidColorBrush)plate.Background).Color.A); Assert.Equal(Visibility.Visible, bar.Visibility); drawer.BarBackgroundEnabled = true; drawer.BarBackgroundColor = "#0000FF"; drawer.BarBackgroundOpacity = 0.5; window.UpdateLayout(); var bg = ((SolidColorBrush)plate.Background).Color; Assert.Equal(Colors.Blue, Color.FromRgb(bg.R, bg.G, bg.B)); Assert.InRange(bg.A, 120, 135); // 50% of 255 Assert.Equal(new Thickness(18, 6, 18, 6), plate.Padding); // 3. The shadow is a real effect on the glyph, and off by default. Assert.Null(label.Effect); drawer.BarTextShadow = true; window.UpdateLayout(); Assert.IsType(label.Effect); // 4. Cancel restores the pre-edit look (white text, no plate). drawer.CancelCommand.Execute(null); window.UpdateLayout(); Assert.Equal(Colors.White, ((SolidColorBrush)label.Foreground).Color); Assert.Equal(0, ((SolidColorBrush)plate.Background).Color.A); } catch (Exception ex) { throw new XunitException(ex.ToString()); // the host otherwise hides the real stack } finally { window?.Close(); MainViewModel.LayoutPathOverride = null; SqliteConnection.ClearAllPools(); try { File.Delete(tempDb); } catch { /* best-effort */ } } } private static T? FindChild(DependencyObject root) where T : DependencyObject { for (var i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++) { var child = VisualTreeHelper.GetChild(root, i); if (child is T match) return match; var deeper = FindChild(child); if (deeper != null) return deeper; } return null; } }