using System; using System.IO; using Xunit; using ytLive.ViewModels; namespace ytLive.Tests; /// /// The top bar's one decision: [ Build Scenes ][ Record / Go Live ]. Build Scenes ships /// highlighted, and it is edit mode — the old EDIT toggle is gone, so "can I edit?" and "which /// world am I in?" cannot disagree. /// [Collection("MainViewModelLayoutPath")] public class TopBarWorldTests { [Fact] public void The_output_stage_belongs_to_the_output_world_and_holds_one_output_at_a_time() { var db = Path.Combine(Path.GetTempPath(), $"llamacasty-outputstage-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = db; try { var vm = new MainViewModel(); // Nothing to arm while building scenes. Assert.False(vm.ShowOutputStage); vm.EnterOutputWorldCommand.Execute(null); Assert.True(vm.IsOutputWorld); Assert.True(vm.IsOffline); Assert.False(vm.IsRecording); Assert.True(vm.ShowOutputStage); // Entering the output world starts with NOTHING chosen (creator, build 729); the pair is // one axis, so arming either drops the other. Assert.False(vm.RecordPillOn); Assert.False(vm.OnAirPillOn); vm.OnAirPillOn = true; Assert.True(vm.OnAirPillOn); Assert.False(vm.RecordPillOn); vm.RecordPillOn = true; Assert.False(vm.OnAirPillOn); vm.RecordPillOn = false; // back to "nothing chosen" for the state checks below // Nothing chosen yet: both output chips, no action. Assert.True(vm.ShowOutputChoice); Assert.False(vm.ShowYouTubeSelected); Assert.False(vm.ShowLocalMp4Selected); Assert.True(vm.YouTubeChipVisible); Assert.True(vm.LocalMp4ChipVisible); // YouTube chosen: its chip stays (red), two actions appear, MP4's chip goes away. vm.SelectYouTubeCommand.Execute(null); Assert.True(vm.ShowYouTubeSelected); Assert.False(vm.ShowOutputChoice); Assert.True(vm.YouTubeChipVisible); Assert.False(vm.LocalMp4ChipVisible); // Local MP4 chosen: it takes the left slot, one action, and Start/End swap on recording. vm.SelectLocalMp4Command.Execute(null); Assert.True(vm.ShowLocalMp4Selected); Assert.False(vm.YouTubeChipVisible); Assert.True(vm.LocalMp4ChipVisible); Assert.True(vm.ShowStartRecordingAction); Assert.False(vm.ShowEndRecordingAction); Assert.Equal("Local MP4", vm.RecordChipText); // Recording: the pill carries REC + the clock, the stage stays up, and the live reality // line does not (its dot/elapsed/stop would be a second set of the same facts). vm.IsRecording = true; Assert.True(vm.ShowOutputStage); Assert.False(vm.RunningVisible); Assert.False(vm.ShowStartRecordingAction); Assert.True(vm.ShowEndRecordingAction); Assert.StartsWith("REC: ", vm.RecordChipText); vm.IsRecording = false; Assert.Equal("Local MP4", vm.RecordChipText); // Neither action exists while building scenes. vm.EnterBuildWorldCommand.Execute(null); Assert.False(vm.ShowStartRecordingAction); Assert.False(vm.ShowEndRecordingAction); Assert.False(vm.YouTubeChipVisible); Assert.False(vm.LocalMp4ChipVisible); } finally { MainViewModel.LayoutPathOverride = null; try { File.Delete(db); } catch (IOException) { } } } /// Closing mid-session must end the session first (2026-11-12). The clear case: with /// nothing running, closing is immediate — no hold, no wait. [Fact] public async Task Closing_with_nothing_running_does_not_hold_the_window() { var db = Path.Combine(Path.GetTempPath(), $"llamacasty-shutdown-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = db; try { var vm = new MainViewModel(); var started = DateTime.UtcNow; Assert.True(await vm.StopBeforeShutdownAsync()); Assert.True(DateTime.UtcNow - started < TimeSpan.FromSeconds(1)); } finally { MainViewModel.LayoutPathOverride = null; try { File.Delete(db); } catch (IOException) { } } } /// /// The symptom the creator hit: pressing Start left the idle chip on screen with a dead command, so /// the recording could not be stopped. Reading the property directly was never the test — the UI must /// be TOLD the stage changed. /// [Fact] public void Starting_or_stopping_a_recording_announces_the_stage_change() { var db = Path.Combine(Path.GetTempPath(), $"llamacasty-recannounce-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = db; try { var vm = new MainViewModel(); vm.EnterOutputWorldCommand.Execute(null); vm.SelectLocalMp4Command.Execute(null); var raised = new System.Collections.Generic.List(); vm.PropertyChanged += (_, e) => raised.Add(e.PropertyName ?? string.Empty); vm.IsRecording = true; Assert.Contains(nameof(MainViewModel.ShowEndRecordingAction), raised); Assert.Contains(nameof(MainViewModel.ShowStartRecordingAction), raised); Assert.Contains(nameof(MainViewModel.RecordChipText), raised); raised.Clear(); vm.IsRecording = false; Assert.Contains(nameof(MainViewModel.ShowStartRecordingAction), raised); Assert.Contains(nameof(MainViewModel.RecordChipText), raised); } finally { MainViewModel.LayoutPathOverride = null; try { File.Delete(db); } catch (IOException) { } } } [Fact] public void Build_scenes_is_the_default_world_and_is_the_only_place_editing_lives() { var db = Path.Combine(Path.GetTempPath(), $"llamacasty-world-{Guid.NewGuid():N}.db"); MainViewModel.LayoutPathOverride = db; try { var vm = new MainViewModel(); // Launch state: Build Scenes highlighted, editing on. Assert.Equal(BarWorld.Build, vm.BarWorld); Assert.True(vm.IsBuildWorld); Assert.False(vm.IsOutputWorld); Assert.True(vm.IsEditMode); // Record / Go Live leaves edit mode — the output surface has nothing to drag. vm.EnterOutputWorldCommand.Execute(null); Assert.Equal(BarWorld.Output, vm.BarWorld); Assert.True(vm.IsOutputWorld); Assert.False(vm.IsBuildWorld); Assert.False(vm.IsEditMode); // Clicking the highlighted half is not a no-op: it re-enters that world, which is how a // creator takes editing back during a session. vm.EnterBuildWorldCommand.Execute(null); Assert.True(vm.IsBuildWorld); Assert.True(vm.IsEditMode); // …and re-entering the world already in force still re-asserts its edit state. vm.EditingActive = false; vm.EnterBuildWorldCommand.Execute(null); Assert.True(vm.IsEditMode); } finally { MainViewModel.LayoutPathOverride = null; try { File.Delete(db); } catch (IOException) { } } } }