Build 730 (creator): - Left pill while recording: solid #2ECC71 dot pulsing 1.0 <-> 0.2 on the existing one-second timer, and "REC: hh:mm:ss" in Consolas so the digits cannot jitter the pill's width. Idle face unchanged. - Right pill while recording: a stop square and "Stop Recording"; clicking it runs the normal stop, which halts and resets the clock and returns the pair to "Local MP4" / "Start Recording". - The separate reality line (dot + REC/LIVE + elapsed + End) is now LIVE-ONLY: a recording would otherwise print the same three facts twice. RunningVisible is IsLive, and ShowOutputStage no longer hides itself for a recording (it must be on screen to carry the clock and the stop). Two rules collided and are now separated rather than merged: a creator's Stop on a recording restores the Local MP4 pair (build 730), while a pipeline FAILURE still clears both pills (the 2026-09-01 zombie rule). StopStream(afterFailure:) carries the difference, and the pump-failure path passes it. RealAppHost.Run was rethrowing with `throw failure;`, which destroyed the original stack — a failing assertion inside the action reported the wrapper's line instead of its own. ExceptionDispatchInfo now preserves it; that is how the two failures above were located in one pass instead of five. 680 pass (one audio-pipe instance flake under full-suite load, passes alone), 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art, still awaiting their visual check.
117 lines
4.7 KiB
C#
117 lines
4.7 KiB
C#
using System;
|
|
using System.IO;
|
|
using Microsoft.Data.Sqlite;
|
|
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.Services;
|
|
using ytLive.ViewModels;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>
|
|
/// Top bar redesign — the bar is ONE surface rendered by the FIRST decision
|
|
/// (Record or Stream). Each world is the whole bar: Test is a child of Stream,
|
|
/// but the account zone (Sign In / avatar) is WORLD-INDEPENDENT (creator
|
|
/// 2026-09-23) so the right column never pops in and out and the bar height
|
|
/// stays pinned. The switch is a two-position gate, so "going back" is one tap
|
|
/// on the other segment. While running, the switch retires and a single reality
|
|
/// line (dot + word + elapsed) + End replace it.
|
|
/// </summary>
|
|
[Collection("RealApp")]
|
|
public sealed class TopBarModeTests
|
|
{
|
|
private readonly RealAppHost _app;
|
|
|
|
public TopBarModeTests(RealAppHost app) => _app = app;
|
|
|
|
[Fact]
|
|
public void TopBar_Renders_Only_The_Armed_World()
|
|
{
|
|
_app.Run(Run);
|
|
}
|
|
|
|
private void Run()
|
|
{
|
|
var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-topbar-{Guid.NewGuid():N}.db");
|
|
MainViewModel.LayoutPathOverride = tempDb;
|
|
try
|
|
{
|
|
using (var schema = new LayoutStore(tempDb)) { }
|
|
SqliteConnection.ClearAllPools();
|
|
|
|
var vm = new MainViewModel();
|
|
|
|
// The dev machine may hold a real saved OAuth session (loads synchronously
|
|
// during the ctor), so the sign-in state is forced deterministically.
|
|
vm.IsConnected = false;
|
|
vm.EditingActive = false; // EDIT off selects Go Live instead of Test Stream.
|
|
|
|
// Default (nothing armed) = the Record world: Go says "Start Recording",
|
|
// no Test — but the account zone is app-level and stays (signed out →
|
|
// Sign In visible even here; no avatar).
|
|
Assert.True(vm.ShowIdleWorld);
|
|
Assert.True(vm.ShowRecordAction);
|
|
Assert.False(vm.ShowStreamActions);
|
|
Assert.True(vm.ShowSignInButton);
|
|
Assert.False(vm.ShowAvatarButton);
|
|
Assert.False(vm.ShowTestButton);
|
|
Assert.Equal("Start Recording", vm.PrimaryStartButtonLabel);
|
|
|
|
// Arming ON-AIR switches the whole bar to the Stream world.
|
|
vm.OnAirPillOn = true;
|
|
Assert.False(vm.ShowRecordAction);
|
|
Assert.True(vm.ShowStreamActions);
|
|
|
|
// Signed out: the account zone shows Sign In; no avatar; Test is gated
|
|
// behind the sign-in (Test rides on Stream AND a connected account).
|
|
Assert.True(vm.ShowSignInButton);
|
|
Assert.False(vm.ShowAvatarButton);
|
|
Assert.False(vm.ShowTestButton);
|
|
Assert.Equal("Go Live", vm.PrimaryStartButtonLabel);
|
|
|
|
// Connected: avatar + Test join the Stream world; Sign In retires.
|
|
vm.IsConnected = true;
|
|
Assert.False(vm.ShowSignInButton);
|
|
Assert.True(vm.ShowAvatarButton);
|
|
Assert.True(vm.ShowTestButton);
|
|
|
|
// Going back is one tap: flipping REC returns the bar to the Record world —
|
|
// the avatar STAYS (world-independent account zone), only Test retires.
|
|
vm.RecordPillOn = true;
|
|
Assert.False(vm.ShowStreamActions);
|
|
Assert.True(vm.ShowRecordAction);
|
|
Assert.True(vm.ShowAvatarButton);
|
|
Assert.False(vm.ShowTestButton);
|
|
Assert.Equal("Start Recording", vm.PrimaryStartButtonLabel);
|
|
|
|
// A local recording STAYS in the output world (build 730): the pill carries REC, the
|
|
// clock and the stop, so the separate reality line stays away — it would be a second set
|
|
// of the same facts.
|
|
vm.EnterOutputWorldCommand.Execute(null);
|
|
vm.RecordPillOn = true;
|
|
vm.OnAirPillOn = false;
|
|
vm.IsRecording = true;
|
|
Assert.True(vm.ShowOutputStage);
|
|
Assert.False(vm.RunningVisible);
|
|
Assert.StartsWith("REC: ", vm.RecordChipText);
|
|
vm.IsRecording = false;
|
|
|
|
// A live stream retires the stage instead; the reality line pins the mode.
|
|
vm.StreamStatus = StreamStatus.Streaming;
|
|
Assert.True(vm.RunningVisible);
|
|
Assert.False(vm.ShowOutputStage);
|
|
Assert.Equal("LIVE", vm.RunningText);
|
|
Assert.Equal("#ff5451", vm.RunningDotBrush);
|
|
vm.IsTestStream = true;
|
|
Assert.Equal("TEST", vm.RunningText);
|
|
Assert.Equal("#9c6f1c", vm.RunningDotBrush);
|
|
}
|
|
finally
|
|
{
|
|
MainViewModel.LayoutPathOverride = null;
|
|
SqliteConnection.ClearAllPools();
|
|
try { File.Delete(tempDb); } catch { /* best-effort cleanup */ }
|
|
}
|
|
}
|
|
}
|