Merge subtask
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using ClaudeDo.Localization;
|
||||
|
||||
namespace ClaudeDo.Installer.Tests;
|
||||
|
||||
internal sealed class FakeLocalizer : ILocalizer
|
||||
{
|
||||
public string this[string key] => key;
|
||||
public string Get(string key, params object[] args) => key;
|
||||
public string CurrentCode { get; private set; } = "en";
|
||||
public IReadOnlyList<LanguageOption> AvailableLanguages { get; } = new[] { new LanguageOption("en", "English") };
|
||||
public void SetLanguage(string code) => CurrentCode = code;
|
||||
public event EventHandler? LanguageChanged;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using ClaudeDo.Installer.Core;
|
||||
using ClaudeDo.Installer.Pages.PathsPage;
|
||||
using ClaudeDo.Installer.Pages.ServicePage;
|
||||
using ClaudeDo.Installer.Pages.SystemCheckPage;
|
||||
using ClaudeDo.Installer.Pages.UiSettingsPage;
|
||||
using ClaudeDo.Installer.Pages.WelcomePage;
|
||||
|
||||
namespace ClaudeDo.Installer.Tests;
|
||||
|
||||
public class PageResolverTests
|
||||
{
|
||||
[Fact]
|
||||
public void SystemCheckPage_is_in_the_wizard_directly_after_welcome()
|
||||
{
|
||||
var context = new InstallContext();
|
||||
var pages = new IInstallerPage[]
|
||||
{
|
||||
new WelcomePageViewModel(context),
|
||||
new SystemCheckPageViewModel(context, () => throw new InvalidOperationException()),
|
||||
new PathsPageViewModel(context),
|
||||
new ServicePageViewModel(context),
|
||||
new UiSettingsPageViewModel(context),
|
||||
};
|
||||
|
||||
var resolver = new PageResolver(pages);
|
||||
var wizardPages = resolver.WizardPages;
|
||||
|
||||
var welcomeIndex = wizardPages.ToList().FindIndex(p => p is WelcomePageViewModel);
|
||||
var systemCheckIndex = wizardPages.ToList().FindIndex(p => p is SystemCheckPageViewModel);
|
||||
|
||||
Assert.NotEqual(-1, welcomeIndex);
|
||||
Assert.Equal(welcomeIndex + 1, systemCheckIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SystemCheckPage_is_not_shown_in_settings()
|
||||
{
|
||||
var context = new InstallContext();
|
||||
var page = new SystemCheckPageViewModel(context, () => throw new InvalidOperationException());
|
||||
|
||||
Assert.False(page.ShowInSettings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using ClaudeDo.Installer.Checks;
|
||||
using ClaudeDo.Installer.Core;
|
||||
|
||||
namespace ClaudeDo.Installer.Tests.Pages.SystemCheckPage;
|
||||
|
||||
internal sealed class FakeEnvironmentCheck : IEnvironmentCheck
|
||||
{
|
||||
private readonly Func<InstallContext, CancellationToken, Task<CheckResult>> _run;
|
||||
|
||||
public FakeEnvironmentCheck(string id, CheckSeverity severity, Func<InstallContext, CancellationToken, Task<CheckResult>> run)
|
||||
{
|
||||
Id = id;
|
||||
Severity = severity;
|
||||
_run = run;
|
||||
}
|
||||
|
||||
public static FakeEnvironmentCheck Ok(string id, CheckSeverity severity = CheckSeverity.Error) =>
|
||||
new(id, severity, (_, _) => Task.FromResult(CheckResult.Ok(id, severity, $"checks.{id}.title", "ok")));
|
||||
|
||||
public static FakeEnvironmentCheck Fail(string id, CheckSeverity severity) =>
|
||||
new(id, severity, (_, _) => Task.FromResult(CheckResult.Fail(id, severity, $"checks.{id}.title", "failed")));
|
||||
|
||||
public static FakeEnvironmentCheck Unknown(string id, CheckSeverity severity = CheckSeverity.Warning) =>
|
||||
new(id, severity, (_, _) => Task.FromResult(CheckResult.Unknown(id, severity, $"checks.{id}.title", "unknown")));
|
||||
|
||||
public string Id { get; }
|
||||
public CheckSeverity Severity { get; }
|
||||
public int CallCount { get; private set; }
|
||||
|
||||
public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
|
||||
{
|
||||
CallCount++;
|
||||
return _run(ctx, ct);
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
using ClaudeDo.Installer.Checks;
|
||||
using ClaudeDo.Installer.Core;
|
||||
using Vm = ClaudeDo.Installer.Pages.SystemCheckPage.SystemCheckPageViewModel;
|
||||
|
||||
namespace ClaudeDo.Installer.Tests.Pages.SystemCheckPage;
|
||||
|
||||
public sealed class SystemCheckPageViewModelTests
|
||||
{
|
||||
private static Vm CreateViewModel(Func<EnvironmentCheckService> factory) =>
|
||||
new(new InstallContext(), factory);
|
||||
|
||||
private static Func<EnvironmentCheckService> FactoryFor(params IEnvironmentCheck[] checks) =>
|
||||
() => new EnvironmentCheckService(checks);
|
||||
|
||||
[Fact]
|
||||
public async Task Entering_the_page_runs_checks_exactly_once()
|
||||
{
|
||||
var check = FakeEnvironmentCheck.Ok("a");
|
||||
var vm = CreateViewModel(FactoryFor(check));
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.Equal(1, check.CallCount);
|
||||
Assert.True(vm.HasRun);
|
||||
Assert.Single(vm.Rows);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Entering_the_page_a_second_time_does_not_run_checks_again()
|
||||
{
|
||||
var check = FakeEnvironmentCheck.Ok("a");
|
||||
var vm = CreateViewModel(FactoryFor(check));
|
||||
|
||||
await vm.LoadAsync();
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.Equal(1, check.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Recheck_while_a_run_is_in_progress_does_not_start_a_second_run()
|
||||
{
|
||||
var gate = new TaskCompletionSource();
|
||||
var slow = new FakeEnvironmentCheck("slow", CheckSeverity.Error, async (_, _) =>
|
||||
{
|
||||
await gate.Task;
|
||||
return CheckResult.Ok("slow", CheckSeverity.Error, "checks.slow.title", "ok");
|
||||
});
|
||||
|
||||
var factoryCalls = 0;
|
||||
var vm = CreateViewModel(() =>
|
||||
{
|
||||
factoryCalls++;
|
||||
return new EnvironmentCheckService(new IEnvironmentCheck[] { slow });
|
||||
});
|
||||
|
||||
var firstRun = vm.RunChecksCommand.ExecuteAsync(null);
|
||||
Assert.True(vm.IsRunning);
|
||||
|
||||
await vm.RunChecksCommand.ExecuteAsync(null); // recheck while the first run is still in flight
|
||||
|
||||
Assert.Equal(1, factoryCalls);
|
||||
Assert.Equal(1, slow.CallCount);
|
||||
|
||||
gate.SetResult();
|
||||
await firstRun;
|
||||
|
||||
Assert.False(vm.IsRunning);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Blocking_error_blocks_navigation_and_clears_after_a_clean_recheck()
|
||||
{
|
||||
var callNumber = 0;
|
||||
var vm = CreateViewModel(() =>
|
||||
{
|
||||
callNumber++;
|
||||
IEnvironmentCheck check = callNumber == 1
|
||||
? FakeEnvironmentCheck.Fail("git", CheckSeverity.Error)
|
||||
: FakeEnvironmentCheck.Ok("git");
|
||||
return new EnvironmentCheckService(new[] { check });
|
||||
});
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.True(vm.HasBlockingError);
|
||||
Assert.True(vm.BlocksNavigation);
|
||||
|
||||
await vm.RunChecksCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.False(vm.HasBlockingError);
|
||||
Assert.False(vm.BlocksNavigation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Only_warnings_does_not_block_navigation()
|
||||
{
|
||||
var warning = FakeEnvironmentCheck.Fail("gitIdentity", CheckSeverity.Warning);
|
||||
var vm = CreateViewModel(FactoryFor(warning));
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.False(vm.HasBlockingError);
|
||||
Assert.False(vm.BlocksNavigation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Only_unknown_results_do_not_block_navigation()
|
||||
{
|
||||
var unknown = FakeEnvironmentCheck.Unknown("claudeAuth", CheckSeverity.Error);
|
||||
var vm = CreateViewModel(FactoryFor(unknown));
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.False(vm.HasBlockingError);
|
||||
Assert.False(vm.BlocksNavigation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Summary_reports_all_ok()
|
||||
{
|
||||
var vm = CreateViewModel(FactoryFor(FakeEnvironmentCheck.Ok("git")));
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.Equal("Everything looks good.", vm.Summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Summary_reports_warning_count()
|
||||
{
|
||||
var vm = CreateViewModel(FactoryFor(
|
||||
FakeEnvironmentCheck.Fail("gitIdentity", CheckSeverity.Warning),
|
||||
FakeEnvironmentCheck.Fail("ports", CheckSeverity.Warning)));
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.Equal("2 warning(s) found.", vm.Summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Summary_reports_blocking_errors_by_name()
|
||||
{
|
||||
var vm = CreateViewModel(FactoryFor(
|
||||
FakeEnvironmentCheck.Fail("git", CheckSeverity.Error),
|
||||
FakeEnvironmentCheck.Fail("writeAccess", CheckSeverity.Error)));
|
||||
|
||||
await vm.LoadAsync();
|
||||
|
||||
Assert.Equal("2 problem(s) must be fixed: checks.git.title, checks.writeAccess.title", vm.Summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Summary_reports_running_state_while_a_check_is_in_flight()
|
||||
{
|
||||
var gate = new TaskCompletionSource();
|
||||
var slow = new FakeEnvironmentCheck("slow", CheckSeverity.Error, async (_, _) =>
|
||||
{
|
||||
await gate.Task;
|
||||
return CheckResult.Ok("slow", CheckSeverity.Error, "checks.slow.title", "ok");
|
||||
});
|
||||
var vm = CreateViewModel(FactoryFor(slow));
|
||||
|
||||
var run = vm.RunChecksCommand.ExecuteAsync(null);
|
||||
|
||||
Assert.True(vm.IsRunning);
|
||||
Assert.Equal("Checking your system…", vm.Summary);
|
||||
|
||||
gate.SetResult();
|
||||
await run;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using ClaudeDo.Installer.Checks;
|
||||
using ClaudeDo.Installer.Core;
|
||||
using ClaudeDo.Installer.Pages.InstallPage;
|
||||
using ClaudeDo.Installer.Pages.PathsPage;
|
||||
using ClaudeDo.Installer.Pages.ServicePage;
|
||||
using ClaudeDo.Installer.Pages.SystemCheckPage;
|
||||
using ClaudeDo.Installer.Pages.UiSettingsPage;
|
||||
using ClaudeDo.Installer.Pages.WelcomePage;
|
||||
using ClaudeDo.Installer.Tests.Pages.SystemCheckPage;
|
||||
using ClaudeDo.Installer.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace ClaudeDo.Installer.Tests;
|
||||
|
||||
public class WizardViewModelTests
|
||||
{
|
||||
private static IReadOnlyList<IInstallerPage> BuildAllPages(InstallContext context)
|
||||
{
|
||||
var emptyServiceProvider = new ServiceCollection().BuildServiceProvider();
|
||||
return new IInstallerPage[]
|
||||
{
|
||||
new WelcomePageViewModel(context),
|
||||
new SystemCheckPageViewModel(context, () => throw new InvalidOperationException("not needed for this test")),
|
||||
new PathsPageViewModel(context),
|
||||
new ServicePageViewModel(context),
|
||||
new UiSettingsPageViewModel(context),
|
||||
new InstallPageViewModel(context, emptyServiceProvider),
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreshInstall_includes_SystemCheckPage_directly_after_welcome()
|
||||
{
|
||||
var context = new InstallContext { Mode = InstallerMode.FreshInstall };
|
||||
var resolver = new PageResolver(BuildAllPages(context));
|
||||
var vm = new WizardViewModel(resolver, context, new FakeLocalizer());
|
||||
|
||||
var pages = vm.Pages.ToList();
|
||||
var welcomeIndex = pages.FindIndex(p => p is WelcomePageViewModel);
|
||||
var systemCheckIndex = pages.FindIndex(p => p is SystemCheckPageViewModel);
|
||||
|
||||
Assert.NotEqual(-1, welcomeIndex);
|
||||
Assert.Equal(welcomeIndex + 1, systemCheckIndex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_mode_does_not_show_SystemCheckPage()
|
||||
{
|
||||
var context = new InstallContext { Mode = InstallerMode.Update };
|
||||
var resolver = new PageResolver(BuildAllPages(context));
|
||||
var vm = new WizardViewModel(resolver, context, new FakeLocalizer());
|
||||
|
||||
Assert.DoesNotContain(vm.Pages, p => p is SystemCheckPageViewModel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanGoNext_is_false_while_the_system_check_page_has_a_blocking_error_and_true_after_a_clean_recheck()
|
||||
{
|
||||
var context = new InstallContext { Mode = InstallerMode.FreshInstall };
|
||||
var callNumber = 0;
|
||||
var systemCheckPage = new SystemCheckPageViewModel(context, () =>
|
||||
{
|
||||
callNumber++;
|
||||
IEnvironmentCheck check = callNumber == 1
|
||||
? FakeEnvironmentCheck.Fail("git", CheckSeverity.Error)
|
||||
: FakeEnvironmentCheck.Ok("git");
|
||||
return new EnvironmentCheckService(new[] { check });
|
||||
});
|
||||
|
||||
var resolver = new PageResolver(new IInstallerPage[]
|
||||
{
|
||||
new WelcomePageViewModel(context),
|
||||
systemCheckPage,
|
||||
});
|
||||
var vm = new WizardViewModel(resolver, context, new FakeLocalizer());
|
||||
|
||||
await vm.GoNextCommand.ExecuteAsync(null); // Welcome -> SystemCheck; auto-run fails
|
||||
|
||||
Assert.Same(systemCheckPage, vm.CurrentPage);
|
||||
Assert.False(vm.CanGoNext);
|
||||
|
||||
await systemCheckPage.RunChecksCommand.ExecuteAsync(null); // recheck comes back clean
|
||||
|
||||
Assert.True(vm.CanGoNext);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user