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);
}
}