using System.Linq; using Xunit; namespace ytLive.Tests; /// The creator's six corrections (2026-10-30) are visual rulings, so the only honest guard /// is one that pins the ruling to the markup: if a later edit quietly reverts one of them, this /// fails and names it. /// /// 1. Stream Info + YPP rails are YouTube surfaces (YouTube red). 2. EDIT shows a real /// off/on difference. 3. The active scene outline is orange again. 4. Scene labels wear the same /// pill as the hotkey chips. 5. REC / ON-AIR and the running stop button read red. 6. The /// properties list no longer caps its height, so no scrollbar appears while space remains. public class UiShellTests { [Fact] public void The_creators_six_ui_corrections_are_in_the_markup() { var preview = SourceAssert.Text("Controls", "PreviewPane.xaml"); var topBar = SourceAssert.Text("Controls", "TopBar.xaml"); var strip = SourceAssert.Text("Controls", "SceneThumbnailStrip.xaml"); var leftPanel = SourceAssert.Text("Controls", "LeftPanel.xaml"); var theme = SourceAssert.Text("Themes", "Controls.xaml"); var tokens = SourceAssert.Text("Themes", "Tokens.xaml"); // 1. YouTube-branded rails. Assert.Contains("Brush.Youtube", tokens); Assert.Equal(2, Count(preview, "Background=\"{StaticResource Brush.Youtube}\"")); // 2. The world segment replaced the EDIT toggle (creator ruling 2026-11-12): both halves are // present, each lit by its own world flag through the shared style. Assert.Contains("Text=\"Build Scenes\"", topBar); Assert.Contains("Text=\"Record / Go Live\"", topBar); Assert.Contains("Tag=\"{Binding IsBuildWorld}\"", topBar); Assert.Contains("Tag=\"{Binding IsOutputWorld}\"", topBar); Assert.DoesNotContain("x:Name=\"EditBody\"", topBar); // 2b. The segment is Build-world only: choosing Record / Go Live REPLACES it with the output // line (creator ruling 2026-11-12), and a chevron is the way back. Assert.Contains("Data=\"M3,4h8v6H3V4zM13,4h8v6h-8V4zM3,12h8v8H3V12zM13,12h8v8h-8V12z\"", topBar); Assert.Contains("Visibility=\"{Binding IsBuildWorld, Converter={StaticResource BoolToVis}}\"", topBar); Assert.Contains("ToolTip=\"Back to Build Scenes\"", topBar); // 2c. The output stage: two labelled switches and the exclusivity promise beside them. Assert.Contains("Text=\"Output\"", topBar); Assert.Contains("Text=\"YouTube\"", topBar); Assert.Contains("Text=\"Local MP4\"", topBar); Assert.Contains("Text=\"One output at a time\"", topBar); Assert.Contains("IsChecked=\"{Binding OnAirPillOn}\"", topBar); Assert.Contains("IsChecked=\"{Binding RecordPillOn}\"", topBar); // 2d. The bar carries NO action button: the output line is the bar's whole middle // (creator ruling 2026-11-12 — the action is placed elsewhere, on instruction). Assert.DoesNotContain("Content=\"Start Recording\"", topBar); Assert.DoesNotContain("Content=\"Private Test Stream\"", topBar); Assert.DoesNotContain("Content=\"Review & Go Live\"", topBar); // 3. Active scene = orange; live stays red. Assert.Contains("IsStaged}\" Value=\"True\">\n ", strip); // 4. Scene name pill, same shape as the hotkey chip. Assert.Contains("Padding=\"8,2\" CornerRadius=\"10\" Background=\"#000000\"", strip); Assert.Contains("Text=\"{Binding Name, Mode=OneWay}\"", strip); // 5. Armed session controls are red, and the running stop button is red — not a grey literal. Assert.Contains("x:Key=\"RecordPillToggle\"", theme); // One rule for session controls: white ink in every state, the fill carries the state. // The missing base foreground is what painted the REC/ON-AIR labels black (2026-10-30). var pill = theme[theme.IndexOf("x:Key=\"RecordPillToggle\"", System.StringComparison.Ordinal)..]; pill = pill[..pill.IndexOf("", System.StringComparison.Ordinal)]; Assert.Contains("", pill); Assert.Contains("Brush.Primary", theme); Assert.Contains("Style=\"{StaticResource RecordPillToggle}\"", topBar); Assert.Contains("Background=\"{StaticResource Brush.Primary}\" Margin=\"14,0,0,0\"", topBar); Assert.DoesNotContain("Background=\"#333\"", topBar); // 6. No artificial height cap on the properties list: the list fills the row, so a // scrollbar appears only when the content genuinely exceeds the space. var listLine = leftPanel.Split('\n').First(line => line.Contains("ElementsScrollViewer")); Assert.DoesNotContain("MaxHeight", listLine); } [Fact] public void The_wordmark_ty_stays_youtube_red_and_ctrl_opens_the_context_menu() { var topBar = SourceAssert.Text("Controls", "TopBar.xaml"); // Adjacent, with nothing between: whitespace inside a TextBlock between inline Runs // collapses to a space and prints "LlamaCas ty" (this exact bug shipped once). Assert.Contains("", topBar); // Plain right-click is the zoom box; the context menu moved to Ctrl+right-click so a // creator can change capture sources mid-stream. var preview = SourceAssert.Text("Controls", "PreviewPane.xaml.cs"); Assert.Contains("ModifierKeys.Control", preview); Assert.Contains("return;", preview); } [Fact] public void Settings_gear_is_orange_on_the_preview_rail_and_EDIT_precedes_the_session_cluster() { var topBar = SourceAssert.Text("Controls", "TopBar.xaml"); var preview = SourceAssert.Text("Controls", "PreviewPane.xaml"); // The settings gear moved from the top bar to the first orange preview-rail tab. var settingsTooltip = preview.IndexOf("ToolTip=\"Settings\"", System.StringComparison.Ordinal); Assert.True(settingsTooltip >= 0, "the Settings gear tab is missing from the preview rail"); var gearStart = preview.LastIndexOf("", settingsTooltip, System.StringComparison.Ordinal); Assert.True(gearStart >= 0 && gearEnd > settingsTooltip, "the Settings tab has no bordered gear surface"); var gear = preview[gearStart..gearEnd]; Assert.Contains("Background=\"{StaticResource Brush.Attention}\"", gear); Assert.Contains("MouseLeftButtonUp=\"SettingsTab_Click\"", gear); Assert.DoesNotContain("ToolTip=\"Settings\"", topBar); // The world segment leads the centre cluster; the brand column carries no edit control. var centre = topBar.IndexOf(" 0 && segment > centre, "the [ Build Scenes ][ Record / Go Live ] segment must lead the centre cluster"); Assert.DoesNotContain("x:Name=\"EditBody\"", topBar); } private static int Count(string haystack, string needle) { var hits = 0; for (var i = haystack.IndexOf(needle, System.StringComparison.Ordinal); i >= 0; i = haystack.IndexOf(needle, i + needle.Length, System.StringComparison.Ordinal)) hits++; return hits; } }