Creator ruling 2026-11-13: "the app does three things. builds. records. live-streams." So the bar holds three buttons that never disappear; the active one is lit and runs its own flow in place, and the other two dim but stay clickable, so there is no state where the creator cannot see where they are or how to step back. Build [Build Scenes] -> editing on, nothing to start YouTube [Live][Private] -> [Start] -> [Stop] (Private = the private-visibility test stream) Record [Start Recording] -> [● REC hh:mm:ss][Stop Recording] Arming and starting are separate presses on purpose (creator): Start is the last chance to tell someone "check you are ready". Collapsing them would save one click and lose that prompt. This replaces the two-world model (BarWorld) and its output stage outright, including the chips added earlier today. The old pills remain the underlying state (OnAirPillOn/RecordPillOn) so the start, stop and step-down paths are untouched; the activity only decides what the bar shows. Closing the app no longer waits 6 s for a session that cannot stop: it waits ~2 s, then clears the state and closes anyway. A window that refuses to close is worse than a stale flag — and that refusal is what hung the suite in ZoomBoxInteractionTests, which sets IsRecording and closes a real window. Test: TopBarActivityTests pins all three flows, the arm-then-start split, and that stepping back to Build forgets the YouTube choice instead of carrying it. TopBarWorldTests is retired with the model it tested.
116 lines
4.7 KiB
C#
116 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 lives in the Record activity (2026-11-13): the bar keeps its three
|
|
// buttons and the Record one carries REC + the clock + Stop, so the separate reality line
|
|
// stays away — it would be a second set of the same facts.
|
|
vm.SelectRecordActivityCommand.Execute(null);
|
|
vm.IsRecording = true;
|
|
Assert.True(vm.IsRecordActivity);
|
|
Assert.True(vm.ShowRecordStop);
|
|
Assert.False(vm.RunningVisible);
|
|
Assert.StartsWith("REC: ", vm.RecordElapsedLabel);
|
|
vm.IsRecording = false;
|
|
|
|
// A live stream is the YouTube activity's flow; the reality line pins the mode.
|
|
vm.SelectYouTubeActivityCommand.Execute(null);
|
|
vm.StreamStatus = StreamStatus.Streaming;
|
|
Assert.True(vm.ShowYouTubeStop);
|
|
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 */ }
|
|
}
|
|
}
|
|
}
|