using ClaudeDo.Ui.ViewModels; using Xunit; namespace ClaudeDo.Ui.Tests.ViewModels.MissionControl; public class InteractiveTerminalViewModelTests { private static readonly DateTime LaunchedAt = new(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); // ── Died-at-startup detection (fix: an immediate ConPTY exit left StartError null, so // ConPtyPaneViewModel.CanRetry — which requires StartError — never offered Retry) ──────── [Fact] public void ProcessExitsNonzero_RightAfterLaunch_SetsStartError() { using var terminal = new InteractiveTerminalViewModel(() => LaunchedAt); terminal.IsRunning = true; // simulates a successful ConPTY spawn terminal.OnSessionProcessExited(null, 1); Assert.False(terminal.IsRunning); Assert.True(terminal.HasExited); Assert.Equal(1, terminal.ExitCode); Assert.NotNull(terminal.StartError); } [Fact] public void ProcessExitsNonzero_AfterRunningAWhile_DoesNotSetStartError() { var exitedAt = LaunchedAt.AddSeconds(10); var callCount = 0; using var terminal = new InteractiveTerminalViewModel(() => callCount++ == 0 ? LaunchedAt : exitedAt); terminal.IsRunning = true; // records LaunchedAt via the first clock call terminal.OnSessionProcessExited(null, 1); // crash after running for a while Assert.True(terminal.HasExited); Assert.Null(terminal.StartError); } [Fact] public void ProcessExitsZero_RightAfterLaunch_DoesNotSetStartError() { using var terminal = new InteractiveTerminalViewModel(() => LaunchedAt); terminal.IsRunning = true; terminal.OnSessionProcessExited(null, 0); // user typed exit / session ended cleanly Assert.True(terminal.HasExited); Assert.Null(terminal.StartError); } }