using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Threading; using System.Xml.Linq; using Xunit; using ytLive.Controls; using ytLive.Helpers; using ytLive.Models; namespace ytLive.Tests; /// TASK 63 — the creator (2026-10-21): *"why does the drop down under the web cam /// properties not show the same style for the border color drop down list box?"* — and the /// reason behind it: *"one of the first things we did was define branding. We established /// look-and-feel standards. And yet you keep ignoring these and I'm wondering why."* /// /// The border colour was a hand-rolled ... button that built a /// ContextMenu in code — never a ComboBox, so the TASK 58 sweep (which only covered /// ComboBox/CheckBox/TextBox) never reached it. This guard holds the fix in place, in three /// parts: /// /// The model — the border colour is a palette entry the drop-down can select by /// VALUE (carrying the colour's name), and a custom hex the creator typed is still offered. /// /// The panel — the picker IS the app's own YtComboBox, the widget every /// other drop-down wears, and the hand-rolled menu is gone from both the XAML and the /// code-behind. The hex field stays, so a colour outside the palette can still be typed. /// /// The widget — with the app's real theme style and the panel's real templates, /// the CLOSED box shows the colour's name and the drop-down ROWS show the hex AND the name. /// (WPF copies ItemTemplate into the read-only SelectionBoxItemTemplate, so the /// two can only differ through .) [Collection("RealApp")] public sealed class BorderColorPickerTests { private readonly RealAppHost _app; public BorderColorPickerTests(RealAppHost app) => _app = app; [Fact] public void The_border_colour_picker_is_the_app_drop_down() { // ── 1. the model: the palette entry, and the custom hex ────────────────── var element = new WebcamSceneConfig { Name = "Webcam" }; var raised = new List(); element.PropertyChanged += (_, e) => raised.Add(e.PropertyName); element.BorderColor = "#e94560"; Assert.Equal("Crimson", element.BorderColorEntry.Name); Assert.Equal("#e94560", element.BorderColorEntry.Hex); Assert.Contains("BorderColorEntry", raised); Assert.Contains("BorderColorOptions", raised); element.BorderColorEntry = BorderColorPalette.All[0]; // picking a row Assert.Equal("#FFFFFF", element.BorderColor); element.BorderColor = "#123456"; // a typed hex Assert.Equal(string.Empty, element.BorderColorEntry.Name); // no name to show Assert.Equal("#123456", element.BorderColorEntry.Hex); Assert.Contains(element.BorderColorOptions, o => o.Hex == "#123456"); element.BorderColor = string.Empty; // no colour yet Assert.Equal(BorderColorPalette.All.Count, element.BorderColorOptions.Count); Assert.DoesNotContain(element.BorderColorOptions, o => string.IsNullOrEmpty(o.Hex)); // ── 2. the panel: the app's drop-down, no hand-rolled menu ─────────────── var xaml = XDocument.Load(SourceAssert.RepoPath("Controls", "LeftPanel.xaml")); var picker = xaml.Descendants() .Where(e => e.Name.LocalName == "ComboBox") .SingleOrDefault(c => (string?)c.Attribute("ItemsSource") == "{Binding SelectedElement.BorderColorOptions}"); Assert.True(picker != null, "the border colour is not a drop-down over the palette"); Assert.True(((string?)picker!.Attribute("Style") ?? string.Empty) .Contains("YtComboBox", StringComparison.Ordinal), "the border-colour drop-down is not styled with YtComboBox"); Assert.Equal("{Binding SelectedElement.BorderColorEntry, Mode=TwoWay}", (string?)picker.Attribute("SelectedItem")); var hexField = xaml.Descendants() .Where(e => e.Name.LocalName == "TextBox") .Any(t => (string?)t.Attribute("Text") == "{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"); Assert.True(hexField, "the hex field is gone — a colour outside the palette can no longer be typed"); var panelSource = SourceAssert.Text("Controls", "LeftPanel.xaml"); var codeBehind = SourceAssert.Text("Controls", "LeftPanel.xaml.cs"); Assert.False(panelSource.Contains("WebcamBorderColorPicker_Click", StringComparison.Ordinal), "the hand-rolled colour menu is back in the panel"); Assert.False(codeBehind.Contains("WebcamBorderColorPicker_Click", StringComparison.Ordinal), "the hand-rolled colour menu is back in the code-behind"); Assert.False(codeBehind.Contains("ColorConverter.ConvertFromString", StringComparison.Ordinal), "a colour is hand-painted in code — the theme paints it"); // ── 3. the widget: the closed box names the colour, the rows show hex + name ── element.BorderColor = "#FFFFFF"; _app.Run(() => { var panel = new LeftPanel(); var selector = panel.Resources["BorderColorTemplates"] as ClosedBoxTemplateSelector; Assert.True(selector != null, "the panel carries no colour templates"); var combo = new ComboBox { Style = (Style)Application.Current.Resources["YtComboBox"], ItemTemplateSelector = selector, ItemsSource = element.BorderColorOptions, SelectedItem = element.BorderColorEntry, }; var window = new Window { Width = 420, Height = 240, Content = combo }; window.Show(); combo.UpdateLayout(); var closed = Texts(combo); Assert.True(closed.Contains("White", StringComparison.Ordinal), $"the closed box does not name the colour: '{closed}'"); Assert.False(closed.Contains("#FFFFFF", StringComparison.Ordinal), $"the closed box repeats the hex the field already shows: '{closed}'"); combo.IsDropDownOpen = true; combo.UpdateLayout(); Dispatcher.CurrentDispatcher.Invoke(() => { }, DispatcherPriority.Loaded); var popup = combo.Template.FindName("PART_Popup", combo) as Popup; var rows = popup?.Child == null ? string.Empty : Texts(popup.Child); Assert.True(rows.Contains("#FFFFFF", StringComparison.Ordinal), $"the drop-down rows do not show the hex: '{rows}'"); Assert.True(rows.Contains("White", StringComparison.Ordinal), $"the drop-down rows do not show the colour's name: '{rows}'"); window.Close(); }); } /// Every text in a visual tree, so a template's content can be read back. private static string Texts(DependencyObject root) { var texts = new List(); void Walk(DependencyObject node) { if (node is TextBlock block) texts.Add(block.Text); for (var i = 0; i < VisualTreeHelper.GetChildrenCount(node); i++) Walk(VisualTreeHelper.GetChild(node, i)); } Walk(root); return string.Join("|", texts); } }