Files
LlamaCasty/ytLive.Tests/UiShellTests.cs
T
gramps 16abb1ddc1 fix(topbar): the output line REPLACES the segment, it does not sit beside it
Creator correction: choosing Record / Go Live must take the [ Build Scenes ][ Record / Go Live ] dual
button away and put the Output line in its place. It was rendering both, segment first, which read as one
long toolbar instead of two worlds.

The segment is now Build-world only. That also retires the padlock glyph added in 6c5cd2f — with the
segment gone in the Output world there is no state left for it to describe, so the branch is removed
rather than left unreachable.

Because the segment was the only route back to the layers, the Output world gains a left chevron
("Back to Build Scenes"), present for the whole world including while a session runs — otherwise
editing during a show (unit 1) would have had no entry point and the bar would have dead-ended.

680 pass, 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the
creator's replacement art, still awaiting their visual check.
2026-11-12 15:48:34 -08:00

131 lines
7.5 KiB
C#

using System.Linq;
using Xunit;
namespace ytLive.Tests;
/// <summary>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.
///
/// <para>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.</para></summary>
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 action follows the armed output, and Test is an explicit ask again.
Assert.Contains("Content=\"Start Recording\"", topBar);
Assert.Contains("Content=\"Private Test Stream\"", topBar);
Assert.Contains("Content=\"Review &amp; Go Live\"", topBar);
// 3. Active scene = orange; live stays red.
Assert.Contains("IsStaged}\" Value=\"True\">\n <Setter Property=\"BorderBrush\" Value=\"{StaticResource Brush.Attention}\"/>", 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("</Style>", System.StringComparison.Ordinal)];
Assert.Contains("<Setter Property=\"Foreground\" Value=\"{StaticResource Brush.OnAccent}\"/>", 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("<Run Text=\"LlamaCas\" Foreground=\"{StaticResource Brush.OnSurface}\"/><Run Text=\"ty\" Foreground=\"{StaticResource Brush.Youtube}\"/>", 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("<Border ", settingsTooltip, System.StringComparison.Ordinal);
var gearEnd = preview.IndexOf("</Border>", 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("<StackPanel Grid.Column=\"1\"", System.StringComparison.Ordinal);
var segment = topBar.IndexOf("Text=\"Build Scenes\"", System.StringComparison.Ordinal);
Assert.True(centre > 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;
}
}