using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using System.Windows.Media; using Xunit; using ytLive.Services.Compositor; namespace ytLive.Tests; /// On-stream graphics cannot read application resources (the frame pump is not the UI /// thread), so their colours live in . That duplication is safe only /// if it is checked: this test proves every brand token there equals the brush in /// Themes/Tokens.xaml, and that the renderers carry no colour literals of their own. /// /// Without it, the broadcast picture would drift away from the UI one "quick fix" at a /// time — the exact failure mode DESIGN.md exists to stop. public class OutputPaletteTests { [Fact] public void The_on_stream_palette_matches_the_theme_and_the_renderers_carry_no_literals() { var brushes = Tokens(); var mirrored = new (string Brush, Color Colour)[] { ("Brush.Primary", OutputPalette.Live), ("Brush.PrimaryContainer", OutputPalette.LiveDark), ("Brush.Secondary", OutputPalette.Amber), ("Brush.Attention", OutputPalette.Attention), ("Brush.Positive", OutputPalette.Positive), ("Brush.Error", OutputPalette.Error), ("Brush.ErrorContainer", OutputPalette.ErrorSurface), ("Brush.Tertiary", OutputPalette.Tertiary), ("Brush.OnSurface", OutputPalette.TextPrimary), ("Brush.OnSurfaceVariant", OutputPalette.TextMuted), ("Brush.Background", OutputPalette.Background), ("Brush.SurfaceContainer", OutputPalette.Surface), ("Brush.SurfaceContainerHigh", OutputPalette.SurfaceHigh), ("Brush.Outline", OutputPalette.Outline), }; foreach (var (brushKey, colour) in mirrored) { Assert.True(brushes.ContainsKey(brushKey), $"{brushKey} missing from Themes/Tokens.xaml"); Assert.True(brushes[brushKey] == colour, $"{brushKey} is {brushes[brushKey]} in the theme but {colour} in OutputPalette — the " + "broadcast picture and the UI have drifted apart"); } // The renderers must not introduce colours of their own; the alert accents belong to // OutputPalette too, so one file owns every on-stream value. foreach (var file in new[] { "Services/AlertRenderer.cs", "Services/Compositor/AlertTickerRenderer.cs", "Services/Compositor/BrandFlashPresenter.cs", }) { var text = SourceAssert.Text(file.Split('/')); Assert.DoesNotContain("Color.FromRgb(0x", text); Assert.DoesNotContain("\"Segoe UI\"", text); } } private static Dictionary Tokens() { var text = SourceAssert.Text("Themes", "Tokens.xaml"); var result = new Dictionary(StringComparer.Ordinal); foreach (Match m in Regex.Matches(text, "x:Key=\"(Brush\\.[^\"]+)\"\\s+Color=\"#([0-9a-fA-F]{6,8})\"")) { var hex = m.Groups[2].Value; if (hex.Length == 8) hex = hex[2..]; // drop the alpha the palette does not mirror var r = int.Parse(hex[..2], NumberStyles.HexNumber, CultureInfo.InvariantCulture); var g = int.Parse(hex[2..4], NumberStyles.HexNumber, CultureInfo.InvariantCulture); var b = int.Parse(hex[4..6], NumberStyles.HexNumber, CultureInfo.InvariantCulture); result[m.Groups[1].Value] = Color.FromRgb((byte)r, (byte)g, (byte)b); } return result; } }