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