using ClaudeDo.Ui.ViewModels.Islands; namespace ClaudeDo.Ui.Tests.ViewModels; public class PrepPanelViewModelTests { private sealed class Worker : StubWorkerClient { public TaskCompletionSource Gate { get; } = new(); public bool Throw { get; set; } public override Task RunDailyPrepNowAsync() => Throw ? Task.FromException(new InvalidOperationException("boom")) : Gate.Task; } [Fact] public async Task PlanDay_sets_PrepOperation_IsRunning_before_the_started_broadcast_arrives() { var worker = new Worker(); var vm = new PrepPanelViewModel(worker); var plan = vm.PlanDayCommand.ExecuteAsync(null); Assert.True(vm.PrepOperation.IsRunning); Assert.False(vm.IsPrepRunning); // the broadcast hasn't fired yet worker.Gate.SetResult(true); await plan; Assert.False(vm.PrepOperation.IsRunning); } [Fact] public async Task PlanDayCommand_CanExecute_is_locked_while_running() { var worker = new Worker(); var vm = new PrepPanelViewModel(worker); var plan = vm.PlanDayCommand.ExecuteAsync(null); Assert.False(vm.PlanDayCommand.CanExecute(null)); Assert.False(vm.IsPlanDayEnabled); worker.Gate.SetResult(true); await plan; Assert.True(vm.PlanDayCommand.CanExecute(null)); Assert.True(vm.IsPlanDayEnabled); } [Fact] public async Task PlanDay_ends_the_operation_when_the_worker_call_throws() { var worker = new Worker { Throw = true }; var vm = new PrepPanelViewModel(worker); await vm.PlanDayCommand.ExecuteAsync(null); Assert.False(vm.PrepOperation.IsRunning); Assert.True(vm.PlanDayCommand.CanExecute(null)); } private sealed class TicketWorker : StubWorkerClient { public TicketSettingsDto? Settings { get; set; } public override Task GetTicketSettingsAsync() => Task.FromResult(Settings); } [Theory] [InlineData("http://api", true, true)] [InlineData("http://api", false, false)] [InlineData(null, true, false)] public async Task TicketsConfigured_needs_base_url_and_token(string? url, bool tokenSet, bool expected) { var worker = new TicketWorker { Settings = new TicketSettingsDto(url, tokenSet) }; var vm = new PrepPanelViewModel(worker); await vm.RefreshTicketsConfiguredAsync(); Assert.Equal(expected, vm.TicketsConfigured); } [Fact] public async Task ImportTicketsAndPlanDay_calls_the_worker_and_shares_the_plan_day_lock() { var worker = new Worker(); var vm = new PrepPanelViewModel(worker); var plan = vm.PlanDayCommand.ExecuteAsync(null); Assert.False(vm.ImportTicketsAndPlanDayCommand.CanExecute(null)); worker.Gate.SetResult(true); await plan; await vm.ImportTicketsAndPlanDayCommand.ExecuteAsync(null); Assert.Equal(1, worker.RunTicketImportNowCalls); } }