Public Access
This commit is contained in:
@@ -7,6 +7,7 @@ using DodoSSH.Client.Session.Tests;
|
||||
using DodoSSH.Client.Ssh;
|
||||
using DodoSSH.Client.Storage;
|
||||
using DodoSSH.Client.Terminal;
|
||||
using DodoSSH.Client.Transfer;
|
||||
using DodoSSH.Crypto;
|
||||
using NSubstitute;
|
||||
|
||||
@@ -54,6 +55,14 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
private VaultSession session = null!;
|
||||
private VaultViewModel vault = null!;
|
||||
|
||||
/// <remarks>
|
||||
/// Over a substitute factory that is never asked for a session. Every shape measured here is one the
|
||||
/// screen is in before a connection exists or after one has failed, which is deliberate: the two panes
|
||||
/// are at their widest with the local one full and the remote one carrying its explanation, and a
|
||||
/// connected pane is the same template with shorter names in it.
|
||||
/// </remarks>
|
||||
private TransfersViewModel transfers = null!;
|
||||
|
||||
private static CancellationToken Token => TestContext.Current.CancellationToken;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -82,12 +91,20 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
// every sync pass out of a suite that is only measuring rectangles.
|
||||
vault = new VaultViewModel(session, workspace, knownHosts, static () => null);
|
||||
|
||||
transfers = new TransfersViewModel(
|
||||
Substitute.For<ISftpSessionFactory>(), TimeProvider.System);
|
||||
|
||||
await SeedAsync();
|
||||
|
||||
// Attached after seeding, so the host picker has something in it and the local pane has listed this
|
||||
// machine's home directory — which is what puts real names of real length into the row template.
|
||||
transfers.Attach(vault, knownHosts);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await transfers.DisposeAsync();
|
||||
await vault.DisposeAsync();
|
||||
knownHosts.Close();
|
||||
await workspace.DisposeAsync();
|
||||
@@ -271,6 +288,73 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
});
|
||||
}
|
||||
|
||||
// ---- The transfers screen ----
|
||||
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The widest thing in this window and the one with the least room to give: two file listings side by
|
||||
/// side, each with four columns, and a queue underneath — all inside 826 pixels once the nav rail has
|
||||
/// taken its column. The header row is the tight part, because it holds a host picker, a password box,
|
||||
/// a button and a chip on one line.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Measured disconnected, which is the state the screen opens in and the one where the local pane is at
|
||||
/// its fullest: it lists this machine's home directory, so the row template is exercised with real names
|
||||
/// of real length rather than with fixtures chosen to fit.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheTransfersScreenFitsBeforeAnythingIsConnected()
|
||||
{
|
||||
await MeasureTransfersAsync(faults => faults.ShouldBeEmpty());
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The queue is the half of this screen that only exists once something has been asked for, so a shape
|
||||
/// nothing puts a row into is a shape never laid out. Three rows, because the row template changes with
|
||||
/// the state: a running one shows a bar and a STOP, a stopped one shows RESUME and DISCARD, and a failed
|
||||
/// one carries the server's own sentence in the column the other two put a byte count in.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The rows are placed directly rather than driven through the queue. What is being measured is the
|
||||
/// template at each state, and running a real transfer to reach those states would put a thread-pool
|
||||
/// hand-off and a filesystem in the middle of a test about rectangles. What the queue does is measured in
|
||||
/// <c>DodoSSH.Client.Transfer.Tests</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheTransfersScreenFitsWithTransfersInTheQueue()
|
||||
{
|
||||
Enqueue(TransferDirection.Download, "artefact.tar.gz", 402_653_184, 149_000_000,
|
||||
TransferState.Running, bytesPerSecond: 6_500_000);
|
||||
|
||||
Enqueue(TransferDirection.Upload, "site-backup-2026-07-30.sql.gz", 8_100_000_000, 3_200_000_000,
|
||||
TransferState.Cancelled);
|
||||
|
||||
Enqueue(TransferDirection.Upload, "deploy.sh", 4_096, 0, TransferState.Failed,
|
||||
failure: "deploy.sh is already in that directory on the host. Rename or remove it first — "
|
||||
+ "nothing here overwrites a file that is already there.");
|
||||
|
||||
transfers.Transfers.Count.ShouldBe(3);
|
||||
|
||||
await MeasureTransfersAsync(faults => faults.ShouldBeEmpty());
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The trust card covers the whole screen, and it is the one thing here a user cannot get past without
|
||||
/// pressing something — so a button of its own that fell outside the window would leave the screen
|
||||
/// permanently blocked.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheTransfersScreenFitsWithTheHostKeyCardShowing()
|
||||
{
|
||||
transfers.PendingHostKey = new HostKeyPresentation(
|
||||
"db.internal", 22, "ssh-ed25519", "SHA256:0123456789abcdefghijklmnopqrstuvwxyzABCDEFG");
|
||||
|
||||
await MeasureTransfersAsync(faults => faults.ShouldBeEmpty());
|
||||
}
|
||||
|
||||
// ---- The chrome ----
|
||||
|
||||
/// <remarks>
|
||||
@@ -389,6 +473,48 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
},
|
||||
Token);
|
||||
|
||||
/// <summary>Lays the transfers screen out at the width it gets beside the nav rail.</summary>
|
||||
private Task MeasureTransfersAsync(Action<IReadOnlyList<string>> assert) =>
|
||||
LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var screen = new TransfersScreen { DataContext = transfers };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
screen, LayoutHarness.ScreenWidth, LayoutHarness.ScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
assert(LayoutHarness.Unreachable(window));
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
},
|
||||
Token);
|
||||
|
||||
/// <summary>Puts one transfer on the queue in a given state, without moving a byte.</summary>
|
||||
private void Enqueue(
|
||||
TransferDirection direction,
|
||||
string name,
|
||||
long length,
|
||||
long transferred,
|
||||
TransferState state,
|
||||
double bytesPerSecond = 0,
|
||||
string? failure = null) =>
|
||||
transfers.Transfers.Add(new TransferRowViewModel(new TransferSnapshot(
|
||||
Guid.CreateVersion7(),
|
||||
direction,
|
||||
name,
|
||||
Path.Combine(Path.GetTempPath(), name),
|
||||
SftpPath.Combine("/srv/releases", name),
|
||||
length,
|
||||
transferred,
|
||||
state,
|
||||
bytesPerSecond,
|
||||
failure)));
|
||||
|
||||
/// <summary>Lays the vault screen out at the width it gets once the nav rail has taken its column.</summary>
|
||||
private Task MeasureVaultAsync(Action<IReadOnlyList<string>> assert) =>
|
||||
OnTheVaultAsync((_, window) => assert(LayoutHarness.Unreachable(window)));
|
||||
|
||||
@@ -487,7 +487,8 @@
|
||||
"CommunityToolkit.Mvvm": "[8.4.2, )",
|
||||
"DodoSSH.Client.Session": "[1.0.0, )",
|
||||
"DodoSSH.Client.Ssh": "[1.0.0, )",
|
||||
"DodoSSH.Client.Terminal": "[1.0.0, )"
|
||||
"DodoSSH.Client.Terminal": "[1.0.0, )",
|
||||
"DodoSSH.Client.Transfer": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"dodossh.client.auth": {
|
||||
@@ -538,6 +539,12 @@
|
||||
"DodoSSH.Client.Ssh": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"dodossh.client.transfer": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"DodoSSH.Client.Ssh": "[1.0.0, )"
|
||||
}
|
||||
},
|
||||
"dodossh.contracts": {
|
||||
"type": "Project"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user