Files
LlamaCasty/ytLive.Tests/UiShellTests.cs
T
gramps 838ba17eaf fix(topbar): the bar carries no action button
Creator ruling 2026-11-12: the action is not part of the output row, and it is not to be placed at all
until they say where. So Start Recording / Private Test Stream / Review & Go Live are out of the bar,
along with the divider that had been separating them.

What the middle of the bar is now: the world segment in Build, and the output line
(Output [YouTube] [Local MP4] One output at a time) in the Record / Go Live world. The view-model
commands (StartRecordingCommand, TestStreamCommand, GoLiveCommand) stay, unused by XAML, because they are
wherever the action lands next.

UiShellTests now asserts the bar contains none of the three action labels, so re-adding one by accident
fails the gate.

Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art,
still awaiting their visual check.
2026-11-12 16:00:50 -08:00

132 lines
7.6 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 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 &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;
}
}