using System; using System.Collections.Generic; using System.Threading; using System.Windows.Media; using Xunit; using ytLive.Models; using ytLive.Services; using ytLive.Services.Compositor; namespace ytLive.Tests; /// The creator (2026-10-30): the social bar was hard-wired white-on-transparent. It now /// takes a text colour, an optional background plate with its own colour and opacity, and a text /// drop shadow — set in the Socials drawer. /// /// These knobs are painting rules, so the test paints the real strip and inspects its /// pixels. Anything less would pass while the bar rendered unchanged. public class SocialBarAppearanceTests { private static readonly List Entries = new() { new SocialEntry { Service = SocialService.YouTube, Handle = "creator", ProfileUrl = "https://youtube.com/@creator" }, }; [Fact] public void The_bar_paints_the_chosen_text_colour_plate_and_shadow() { Exception? failure = null; var results = new List(); var thread = new Thread(() => { try { // 1. Default look: white glyphs, no plate, no shadow. var plain = Render(SocialBarStyle.Default); Assert.NotNull(plain); Assert.True(Count(plain!, (r, g, b, a) => r > 200 && g > 200 && b > 200 && a > 200) > 0, "white glyphs must be painted by default"); Assert.Equal(0, Count(plain!, (r, g, b, a) => b > 150 && r < 80 && g < 80)); // 2. Text colour: red glyphs, nothing white. var red = Render(new SocialBarStyle("#FF0000", false, "#000000", 0.55, false)); Assert.True(Count(red!, (r, g, b, a) => r > 180 && g < 80 && b < 80 && a > 200) > 0, "the text colour must reach the glyph pixels"); Assert.Equal(0, Count(red!, (r, g, b, a) => r > 200 && g > 200 && b > 200 && a > 200)); // 3. Plate: blue at 55% — half-transparent blue pixels must appear. var plated = Render(new SocialBarStyle("#FFFFFF", true, "#0000FF", 0.55, false)); Assert.True(Count(plated!, (r, g, b, a) => b > 150 && r < 60 && g < 60 && a is > 100 and < 170) > 200, "the plate must fill with its colour at the chosen opacity"); // 4. Shadow: dark semi-transparent pixels appear beside the glyphs. var shadowed = Render(new SocialBarStyle("#FFFFFF", false, "#000000", 0.55, true)); Assert.True(Count(shadowed!, (r, g, b, a) => r < 60 && g < 60 && b < 60 && a is > 0 and < 200) > Count(plain!, (r, g, b, a) => r < 60 && g < 60 && b < 60 && a is > 0 and < 200), "the drop shadow must add dark pixels behind the glyphs"); } catch (Exception ex) { failure = ex; } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); Assert.Null(failure); } [Fact] public void The_preview_bar_is_never_hidden_by_the_plate_switch() { // Regression, 2026-10-30: the plate Border wrapped the whole bar and its Visibility was // bound to BarBackgroundEnabled, so turning the plate off (the default) removed the bar // from the preview entirely. "Plate off" must mean a transparent plate. var preview = SourceAssert.Text("Controls", "PreviewPane.xaml"); Assert.DoesNotContain("Socials.BarBackgroundEnabled, Converter={StaticResource BoolToVis}", preview); Assert.Contains("{Binding SocialBarBackgroundBrush}", preview); // The plate's PADDING is what switches with the toggle, not the element's presence. Assert.Contains("", preview); } private static VideoFrame? Render(SocialBarStyle style) => SocialBarRenderer.Render(Entries, style); private static int Count(VideoFrame frame, Func predicate) { var hits = 0; for (var i = 0; i < frame.BgraPixels.Length; i += 4) if (predicate(frame.BgraPixels[i + 2], frame.BgraPixels[i + 1], frame.BgraPixels[i], frame.BgraPixels[i + 3])) hits++; return hits; } }