Public Access
176 lines
6.3 KiB
C#
176 lines
6.3 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Headless;
|
|
using Avalonia.Input;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
using DodoSSH.Client.App.Views;
|
|
using DodoSSH.Client.Session;
|
|
using DodoSSH.Client.Session.Tests;
|
|
using DodoSSH.Client.Shell.ViewModels;
|
|
using DodoSSH.Client.Ssh;
|
|
using DodoSSH.Client.Storage;
|
|
using DodoSSH.Client.Terminal;
|
|
|
|
namespace DodoSSH.Client.App.Layout.Tests;
|
|
|
|
/// <summary>
|
|
/// The keyboard on the questions the window asks over itself: where it is when one appears, and what Escape
|
|
/// answers.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// They used to appear with the keyboard wherever it had been, Escape did nothing, and Tab walked the page
|
|
/// behind them. The answer they are given here is always the harmless one — see <see cref="SafeAnswerFocus"/>
|
|
/// — and that is what each test pins: not merely that focus moved, but which button it moved to.
|
|
/// </remarks>
|
|
public sealed class ConfirmationKeyboardTests
|
|
{
|
|
private static CancellationToken Token => TestContext.Current.CancellationToken;
|
|
|
|
[Fact]
|
|
public async Task TheSignOutQuestion_TakesTheKeyboardOnCancel_AndEscapeSaysNo()
|
|
{
|
|
using var caches = await EnrolledAsync();
|
|
|
|
await using var workspace = WholeWindow.Workspace();
|
|
await using var shell = WholeWindow.Shell(caches, workspace);
|
|
|
|
await shell.StartAsync(Token);
|
|
|
|
await LayoutHarness.OnTheUiThreadAsync(
|
|
() => WholeWindow.Show(shell, window =>
|
|
{
|
|
shell.SignOutCommand.Execute(null);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
var card = window.GetVisualDescendants().OfType<SignOutCard>().Single(card => card.IsEffectivelyVisible);
|
|
|
|
window.FocusManager!.GetFocusedElement().ShouldBeSameAs(card.FindControl<Button>("CancelButton"));
|
|
|
|
window.KeyPressQwerty(PhysicalKey.Escape, RawInputModifiers.None);
|
|
|
|
shell.IsConfirmingSignOut.ShouldBeFalse();
|
|
}),
|
|
Token);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AnUnknownHostKey_TakesTheKeyboardOnCancel_KeepsTabInside_AndEscapeRefuses()
|
|
{
|
|
using var caches = await EnrolledAsync();
|
|
|
|
await using var workspace = WholeWindow.Workspace();
|
|
await using var shell = await UnlockedAsync(caches, workspace);
|
|
|
|
var vault = shell.Vault.ShouldNotBeNull();
|
|
|
|
await LayoutHarness.OnTheUiThreadAsync(
|
|
() => WholeWindow.Show(shell, window =>
|
|
{
|
|
vault.PendingHostKey = new HostKeyPresentation("web.example", 22, "ssh-ed25519", "SHA256:abc");
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
var card = window.FindControl<HostKeyCard>("HostKeyPane").ShouldNotBeNull();
|
|
var focused = window.FocusManager!.GetFocusedElement().ShouldBeOfType<Button>();
|
|
|
|
focused.Content.ShouldBe("CANCEL");
|
|
|
|
for (var press = 0; press < 4; press++)
|
|
{
|
|
window.KeyPressQwerty(PhysicalKey.Tab, RawInputModifiers.None);
|
|
|
|
window.FocusManager.GetFocusedElement().ShouldBeAssignableTo<Visual>()
|
|
.ShouldNotBeNull().GetVisualAncestors().ShouldContain(card);
|
|
}
|
|
|
|
window.KeyPressQwerty(PhysicalKey.Escape, RawInputModifiers.None);
|
|
|
|
vault.HasPendingHostKey.ShouldBeFalse();
|
|
}),
|
|
Token);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ADeletionQuestion_TakesTheKeyboardOnCancel_AndEscapeSaysNo()
|
|
{
|
|
using var caches = await EnrolledAsync();
|
|
|
|
await using var workspace = WholeWindow.Workspace();
|
|
await using var shell = await UnlockedAsync(caches, workspace);
|
|
|
|
var vault = shell.Vault.ShouldNotBeNull();
|
|
|
|
await LayoutHarness.OnTheUiThreadAsync(
|
|
() => WholeWindow.Show(shell, window =>
|
|
{
|
|
shell.EnterSettingsCommand.Execute(SettingsPage.Tags);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
vault.PendingDeletion = new DeletionRequest(
|
|
DeletionTarget.Tag, Guid.CreateVersion7(), "Delete the tag prod?", "It goes.", string.Empty);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
var card = window.GetVisualDescendants().OfType<ConfirmDeleteCard>().Single(card => card.IsEffectivelyVisible);
|
|
|
|
window.FocusManager!.GetFocusedElement().ShouldBeSameAs(card.FindControl<Button>("CancelButton"));
|
|
|
|
window.KeyPressQwerty(PhysicalKey.Escape, RawInputModifiers.None);
|
|
|
|
vault.IsConfirmingDeletion.ShouldBeFalse();
|
|
shell.IsSettingsMode.ShouldBeTrue();
|
|
}),
|
|
Token);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EscapeOnTheHostsScreen_PutsTheDrawerAway_LikeItsOwnArrow()
|
|
{
|
|
using var caches = await EnrolledAsync();
|
|
|
|
await using var workspace = WholeWindow.Workspace();
|
|
await using var shell = await UnlockedAsync(caches, workspace);
|
|
|
|
var vault = shell.Vault.ShouldNotBeNull();
|
|
|
|
await LayoutHarness.OnTheUiThreadAsync(
|
|
() => WholeWindow.Show(shell, window =>
|
|
{
|
|
vault.NewHostCommand.Execute(null);
|
|
Dispatcher.UIThread.RunJobs();
|
|
|
|
vault.IsDrawerOpen.ShouldBeTrue();
|
|
|
|
window.KeyPressQwerty(PhysicalKey.Escape, RawInputModifiers.None);
|
|
|
|
vault.IsDrawerOpen.ShouldBeFalse();
|
|
vault.IsEditing.ShouldBeFalse();
|
|
}),
|
|
Token);
|
|
}
|
|
|
|
private static async Task<ClientCacheFactory> EnrolledAsync()
|
|
{
|
|
var caches = ClientCacheFactory.ForMemory($"confirm-{Guid.CreateVersion7():N}");
|
|
await caches.MigrateAsync(Token);
|
|
|
|
await new AccountProvisioner(new FakeAccountServer(), new StubKeyBinding(), caches, TimeProvider.System, WholeWindow.CheapProfile)
|
|
.EnrollAsync("https://dodossh.example", WholeWindow.Passphrase, "laptop", "Personal", Token);
|
|
|
|
return caches;
|
|
}
|
|
|
|
private static async Task<MainWindowViewModel> UnlockedAsync(ClientCacheFactory caches, TerminalWorkspace workspace)
|
|
{
|
|
var shell = WholeWindow.Shell(caches, workspace);
|
|
|
|
await shell.StartAsync(Token);
|
|
|
|
shell.Passphrase = WholeWindow.Passphrase;
|
|
await shell.UnlockCommand.ExecuteAsync(null);
|
|
|
|
shell.IsUnlocked.ShouldBeTrue();
|
|
|
|
return shell;
|
|
}
|
|
}
|