using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.VisualTree;
using DodoSSH.Client.App.Views;
using DodoSSH.Client.Session;
using DodoSSH.Client.Shell.ViewModels;
using DodoSSH.Client.Ssh;
using DodoSSH.Client.Storage;
using DodoSSH.Client.Terminal;
using NSubstitute;
namespace DodoSSH.Client.App.Layout.Tests;
///
/// The titlebar's own search pill still opens the quick-connect palette after v5b's redraw.
///
///
/// A narrow suite on purpose: already holds the bar to its declared
/// height and proves nothing inside it is unreachable, and the search pill's own accent-on-hover border is
/// a style rule with nothing to assert headlessly. What is worth a gesture is the one thing a fidelity pass
/// could quietly break without any layout test noticing — the pill no longer opening what Ctrl+K opens.
///
public sealed class TitleBarTests : IAsyncLifetime
{
private ClientCacheFactory caches = null!;
private TerminalWorkspace workspace = null!;
private MainWindowViewModel shell = null!;
private static CancellationToken Token => TestContext.Current.CancellationToken;
///
public ValueTask InitializeAsync()
{
caches = ClientCacheFactory.ForMemory($"titlebar-{Guid.CreateVersion7():N}");
workspace = new TerminalWorkspace(
new InMemoryTerminalAssetProvider(new Dictionary(StringComparer.Ordinal)),
Substitute.For(),
TimeProvider.System);
shell = new MainWindowViewModel(
ClientPaths.Default,
caches,
workspace,
new VaultKnownHostStore(),
Substitute.For(),
(_, _) => throw new NotSupportedException("nothing here signs in"),
TimeProvider.System,
Substitute.For())
{
// The search pill is disabled while locked — see TitleBar.axaml's IsEnabled binding — and this
// is the only state that gesture is reachable in.
State = ShellState.Unlocked,
};
return ValueTask.CompletedTask;
}
///
public async ValueTask DisposeAsync()
{
await shell.DisposeAsync();
await workspace.DisposeAsync();
caches.Dispose();
}
///
/// A double-click on the bar maximises, and a second one restores. It was a DoubleTapped handler
/// beside a press that started a drag, and the drag handed the press to the operating system's move loop,
/// which ate the release a double tap is assembled from — so on Windows the click often did nothing. The
/// press decides now; see CaptionPresses. A single press must still be a drag and nothing else.
///
[Fact]
public async Task DoubleClickingTheBar_MaximisesAndRestores_AndASinglePressDoesNot()
{
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var bar = new TitleBar { DataContext = shell };
var window = LayoutHarness.HostAtMinimumSize(
bar, LayoutHarness.MinimumWidth, LayoutHarness.TitleBarHeight);
try
{
// The top edge, past everything the bar draws: the drag surface and nothing else.
var empty = new Point(bar.Bounds.Width * 0.6, 2);
window.MouseDown(empty, MouseButton.Left);
window.MouseUp(empty, MouseButton.Left);
window.WindowState.ShouldBe(WindowState.Normal, "one press is a drag");
window.MouseDown(empty, MouseButton.Left);
window.MouseUp(empty, MouseButton.Left);
window.WindowState.ShouldBe(WindowState.Maximized);
window.MouseDown(empty, MouseButton.Left);
window.MouseUp(empty, MouseButton.Left);
window.MouseDown(empty, MouseButton.Left);
window.MouseUp(empty, MouseButton.Left);
window.WindowState.ShouldBe(WindowState.Normal);
}
finally
{
window.Close();
}
},
Token);
}
[Fact]
public async Task ClickingTheSearchPill_OpensTheQuickConnectPalette()
{
await LayoutHarness.OnTheUiThreadAsync(
() =>
{
var bar = new TitleBar { DataContext = shell };
var window = LayoutHarness.HostAtMinimumSize(
bar, LayoutHarness.MinimumWidth, LayoutHarness.TitleBarHeight);
try
{
shell.IsSearching.ShouldBeFalse("nothing has been pressed yet");
var pill = bar.GetVisualDescendants()
.OfType