Creator correction (2026-11-13): "OpenCode literally inserted Live and Private as two extra flat siblings
inside the outer pill bar, shoving Record to the right and making it look like five ungrouped toolbar
items". Correct — completion states were being added to the BAR instead of to a SLOT.
Now the bar is exactly three slots, and the bar's shape never changes:
[ Build Scenes ] [ YouTube ] [ Record ]
YouTube: ▶ YouTube -> [Live | Private | ✕] -> [● Start Live | ✕] (amber: next click goes on air)
-> [■ Stop Live]
Record: ◉ Record -> [● Start Recording | ✕] -> ● REC hh:mm:ss [■ Stop]
Build: enabled only while BOTH action slots are Idle; otherwise disabled at 40% with the tooltip
"Scene editing is locked while streaming or recording"
State lives in YouTubeSlotState / RecordSlotState, with one RaiseSlotStates() announcing every flag so no
caller can forget one. Esc and the ✕ cancel the slot you are in and nothing else.
This retires the BarWorld model from earlier today, along with TopBarActivityTests (superseded by
TopBarSlotTests, which pins all three flows, the lock, and cancel-to-Idle).
679 pass, 0 failed (the host-hang fallback in f4caea0 counted the per-test lines).
Scope justification: TopBarActivityTests.cs is deleted as part of this unit — it tested the model being
replaced; TopBarSlotTests is its successor.
137 lines
7.8 KiB
C#
137 lines
7.8 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=\"YouTube\"", topBar);
|
|
Assert.Contains("Text=\"Record\"", topBar);
|
|
Assert.Contains("IsEnabled=\"{Binding IsBuildScenesEnabled}\"", topBar);
|
|
Assert.Contains("Command=\"{Binding OpenYouTubeCommand}\"", 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);
|
|
// Segmented switcher (creator mock 2026-11-12): pill track, two equal halves, coral active icon.
|
|
Assert.Contains("Brush.SegmentTrack", topBar);
|
|
Assert.Contains("Style=\"{StaticResource SegmentHalf}\"", topBar);
|
|
Assert.Contains("Style=\"{StaticResource SegmentIcon}\"", topBar);
|
|
|
|
|
|
|
|
Assert.DoesNotContain("ToolTip=\"Back to Build Scenes\"", topBar); // Esc replaced the chevron
|
|
|
|
// 2c. The output stage: two labelled switches and the exclusivity promise beside them.
|
|
|
|
Assert.Contains("Text=\"YouTube\"", topBar);
|
|
Assert.Contains("Command=\"{Binding OpenYouTubeCommand}\"", topBar);
|
|
Assert.Contains("Command=\"{Binding OpenRecordCommand}\"", 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 <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 OutputChip}\"", 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 three-activity 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;
|
|
}
|
|
}
|