Public Access
Give the window its v5b chrome and each session surface its own shell
This commit is contained in:
@@ -825,7 +825,10 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
|
||||
transfers.ShowsNoBuckets.ShouldBeTrue("this vault has no buckets in it");
|
||||
|
||||
await MeasureTransfersAsync(faults => faults.ShouldBeEmpty("with nothing to open yet"));
|
||||
// The plain-screen budget, not the session shell's: MainWindow.axaml gives S3 the same TransfersScreen
|
||||
// control with no tab row, no header and no sidebar around it — see its own remark on why the S3
|
||||
// usage is "deliberately not given the session shell above."
|
||||
await MeasureBucketsAsync(faults => faults.ShouldBeEmpty("with nothing to open yet"));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
@@ -1144,18 +1147,30 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
// ---- The transfers screen ----
|
||||
//
|
||||
// ◆ WAVE C's OWN BUDGET. This screen carries two real widths, not one, because the same TransfersScreen
|
||||
// control sits in two different real containers — see LayoutHarness.SessionScreenWidth's own remark and
|
||||
// MainWindow.axaml. On SFTP it is inside wave B's session shell: a 26px padded column, a 1px bordered
|
||||
// container, and — once Transfers.IsConnected — a 300px QUICK ACCESS sidebar squeezed in beside it. On S3
|
||||
// it is the plain screen it always was, at the roomier LayoutHarness.ScreenWidth/ScreenHeight budget; see
|
||||
// TheS3ScreenFitsWithNoBucketsToOpen and MeasureBucketsAsync. Every test below this point measures the
|
||||
// SFTP usage through MeasureTransfersAsync, which reads transfers.IsConnected itself to decide whether
|
||||
// the sidebar is squeezing the pane — the same fact ShowsQuickAccessSidebar reads.
|
||||
|
||||
/// <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.
|
||||
/// The widest this screen gets and the one with the least room to give once connected: two file listings
|
||||
/// side by side, a 64-pixel arrow column between them, and a queue underneath — inside 472 pixels once
|
||||
/// the session shell's own padding, border and QUICK ACCESS sidebar have all been taken out. That leaves
|
||||
/// 204 pixels a side, which is the width every restyled row template in <c>TransfersScreen.axaml</c> was
|
||||
/// actually chosen against; see its own remark on the column widths.
|
||||
/// </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.
|
||||
/// Measured disconnected here, though, 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. Disconnected also
|
||||
/// means no sidebar yet, so this particular test is measured at the roomier 772-pixel shape;
|
||||
/// <see cref="TheTransfersScreenFitsWithASessionOpen"/> below is the one that reaches the 472-pixel one.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
@@ -1253,14 +1268,18 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The state the screen is in once something is open, and the reason it is worth a test of its own is
|
||||
/// the strip the connect bar left behind: a chip naming the account and the endpoint, the status line,
|
||||
/// and DISCONNECT — inside a pane that is under 400 pixels wide at the window's minimum, above a row
|
||||
/// that already carries UP, REFRESH and DELETE.
|
||||
/// The state the screen is in once something is open, and the tightest one wave C's restyle has to
|
||||
/// survive: <c>Transfers.IsConnected</c> is exactly what pulls <c>SessionSidebar</c> into view beside
|
||||
/// this screen — see <c>ShowsQuickAccessSidebar</c> — so this is the test that actually reaches the
|
||||
/// 472-pixel budget <see cref="LayoutHarness.SessionScreenWidth"/> computes, 204 pixels a side. DISCONNECT
|
||||
/// is what is left in the remote pane's own connected strip now; the account-at-host chip that used to
|
||||
/// share the row with it moved out, because <c>SessionHeader</c> already prints the same address above
|
||||
/// this screen — see <c>TransfersScreen.axaml</c>'s own remark on the strip for why keeping both was the
|
||||
/// thing squeezing DISCONNECT off the edge at this width.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The address is a long one deliberately. It is the part of that row with no fixed width, and a chip
|
||||
/// that grew to fit whatever it was given is how the button beside it goes off the edge.
|
||||
/// The address is a long one deliberately, because <c>SessionAddress</c> still has to hold it without
|
||||
/// trimming where the header prints it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
@@ -1303,6 +1322,214 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
await MeasureTransfersAsync(faults => faults.ShouldBeEmpty());
|
||||
}
|
||||
|
||||
// ---- ◆ wave C: the restyled panes and the TRANSFERS strip ----
|
||||
|
||||
/// <remarks>
|
||||
/// The row template's own SIZE and MODIFIED columns, proved against real bindings rather than against
|
||||
/// the fixture's own home directory — whose contents this suite does not control — by adding one row of
|
||||
/// each pane's own shape directly and reading the rendered <c>TextBlock</c>s back. PERMS stays remote-only,
|
||||
/// which is what <see cref="RemoteEntryRowViewModel"/> carrying it and <see cref="LocalEntryRowViewModel"/>
|
||||
/// not proves alongside the two shared columns.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheRowTemplateShowsSizeAndDateColumns()
|
||||
{
|
||||
var local = new LocalEntryRowViewModel(new LocalEntry(
|
||||
"notes.txt",
|
||||
Path.Combine(Path.GetTempPath(), "notes.txt"),
|
||||
IsDirectory: false,
|
||||
Length: 4_096,
|
||||
new DateTimeOffset(2026, 7, 21, 9, 0, 0, TimeSpan.Zero)));
|
||||
|
||||
// Cleared first, not merely appended to: this fixture's real home directory can hold far more entries
|
||||
// than the pane's own viewport, and a virtualizing ListBox only realises the rows that fit in it. A
|
||||
// row added after all of those would never be built at all, which is a false pass rather than a
|
||||
// proof — the assertion below would find nothing to have missed.
|
||||
transfers.LocalEntries.Clear();
|
||||
transfers.LocalEntries.Add(local);
|
||||
|
||||
var remote = new RemoteEntryRowViewModel(new SftpEntry(
|
||||
"deploy.log",
|
||||
"/srv/releases/deploy.log",
|
||||
SftpEntryKind.File,
|
||||
49_152,
|
||||
new DateTimeOffset(2026, 7, 30, 14, 0, 0, TimeSpan.Zero),
|
||||
"-rw-r--r--"));
|
||||
|
||||
transfers.RemoteEntries.Add(remote);
|
||||
|
||||
await LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var screen = new TransfersScreen { DataContext = transfers };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
screen, LayoutHarness.SessionScreenWidth(sidebarVisible: false), LayoutHarness.SessionScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
var texts = screen.GetVisualDescendants().OfType<TextBlock>().Select(text => text.Text).ToList();
|
||||
|
||||
texts.ShouldContain(local.Size, customMessage: "the local pane's own SIZE column");
|
||||
texts.ShouldContain(local.Modified, customMessage: "the local pane's own MODIFIED column");
|
||||
texts.ShouldContain(remote.Size, customMessage: "the remote pane's own SIZE column");
|
||||
texts.ShouldContain(remote.Modified, customMessage: "the remote pane's own MODIFIED column");
|
||||
texts.ShouldContain(remote.Permissions, customMessage: "PERMS, which stays remote-only");
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
},
|
||||
Token);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// <see cref="TransferRowViewModel.StatusWord"/> is what v5b's TRANSFERS strip prints in place of the old
|
||||
/// all-caps <c>StateLabel</c> chip — a live percentage while running, a plain word otherwise — and this
|
||||
/// holds the mapping against every state <c>FileTransferQueue</c> actually reports, at the view model and
|
||||
/// rendered onto the strip itself.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheTransferStripsStatusWordsMapTheQueuesRealStates()
|
||||
{
|
||||
Enqueue(TransferDirection.Upload, "queued.txt", 1_000, 0, TransferState.Queued);
|
||||
Enqueue(TransferDirection.Download, "running.bin", 1_000, 640, TransferState.Running, bytesPerSecond: 1_000);
|
||||
Enqueue(TransferDirection.Upload, "done.txt", 1_000, 1_000, TransferState.Completed);
|
||||
Enqueue(TransferDirection.Upload, "stopped.txt", 1_000, 200, TransferState.Cancelled);
|
||||
Enqueue(TransferDirection.Upload, "failed.txt", 1_000, 0, TransferState.Failed, failure: "refused");
|
||||
|
||||
var expected = new[] { "queued", "64%", "done", "stopped", "failed" };
|
||||
|
||||
transfers.Transfers.Select(row => row.StatusWord).ShouldBe(expected);
|
||||
|
||||
await LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var screen = new TransfersScreen { DataContext = transfers };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
screen, LayoutHarness.SessionScreenWidth(sidebarVisible: false), LayoutHarness.SessionScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
var words = screen.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.Where(text => text.Classes.Contains("transferstatus"))
|
||||
.Select(text => text.Text ?? string.Empty)
|
||||
.ToList();
|
||||
|
||||
words.ShouldBe(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
},
|
||||
Token);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The TRANSFERS strip's own "collapse" — not to zero, but down to the honest sentence the full strip
|
||||
/// carried when nothing had ever been queued. Both shapes are checked: the header and its count chip are
|
||||
/// absent with nothing queued, and a queued transfer brings the full strip straight back.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheTransfersStripCollapsesWhenTheQueueIsEmpty()
|
||||
{
|
||||
transfers.Transfers.ShouldBeEmpty("the fixture queues nothing before this test adds any");
|
||||
|
||||
await LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var screen = new TransfersScreen { DataContext = transfers };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
screen, LayoutHarness.SessionScreenWidth(sidebarVisible: false), LayoutHarness.SessionScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
var label = screen.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.Single(text => text.Classes.Contains("label") && text.Text == "TRANSFERS");
|
||||
|
||||
label.IsEffectivelyVisible.ShouldBeFalse("the full strip collapses with nothing queued");
|
||||
|
||||
var collapsed = screen.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.Single(text => text.Text is { } spoken
|
||||
&& spoken.StartsWith("Nothing queued.", StringComparison.Ordinal));
|
||||
|
||||
collapsed.IsEffectivelyVisible.ShouldBeTrue(
|
||||
"the honest sentence stays reachable in the collapsed shape");
|
||||
|
||||
LayoutHarness.Unreachable(window).ShouldBeEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
},
|
||||
Token);
|
||||
|
||||
Enqueue(TransferDirection.Upload, "queued.txt", 1_000, 0, TransferState.Queued);
|
||||
|
||||
await LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var screen = new TransfersScreen { DataContext = transfers };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
screen, LayoutHarness.SessionScreenWidth(sidebarVisible: false), LayoutHarness.SessionScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
var label = screen.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.Single(text => text.Classes.Contains("label") && text.Text == "TRANSFERS");
|
||||
|
||||
label.IsEffectivelyVisible.ShouldBeTrue("a queued transfer brings the full strip back");
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
},
|
||||
Token);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The narrowest real shape wave C's restyle has to survive at once: connected, so QUICK ACCESS's own
|
||||
/// sidebar takes its 300 pixels — see <see cref="LayoutHarness.SessionScreenWidth"/> — a populated remote
|
||||
/// listing carrying every colour state a row can show (a directory, an executable, a world-writable
|
||||
/// file), and a full transfer queue underneath, all inside the 204-pixel-a-side budget that leaves either
|
||||
/// pane.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheRestyledPanesFitTheSessionShellsNarrowestBudget()
|
||||
{
|
||||
transfers.IsConnected = true;
|
||||
transfers.ConnectedTo = "deployment-service@releases.eu-west.internal.example:2222";
|
||||
|
||||
transfers.RemoteEntries.Add(new RemoteEntryRowViewModel(new SftpEntry(
|
||||
"docker-compose.yml", "/srv/releases/site/docker-compose.yml", SftpEntryKind.File,
|
||||
3_400, TimeProvider.System.GetUtcNow(), "-rw-r--r--")));
|
||||
transfers.RemoteEntries.Add(new RemoteEntryRowViewModel(new SftpEntry(
|
||||
"deploy.sh", "/srv/releases/site/deploy.sh", SftpEntryKind.File,
|
||||
912, TimeProvider.System.GetUtcNow(), "-rwxr-xr-x")));
|
||||
transfers.RemoteEntries.Add(new RemoteEntryRowViewModel(new SftpEntry(
|
||||
"shared", "/srv/releases/site/shared", SftpEntryKind.Directory,
|
||||
0, TimeProvider.System.GetUtcNow(), string.Empty)));
|
||||
|
||||
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);
|
||||
|
||||
await MeasureTransfersAsync(
|
||||
faults => faults.ShouldBeEmpty("connected, with populated rows and a full queue at once"));
|
||||
}
|
||||
|
||||
// ---- The chrome ----
|
||||
|
||||
/// <remarks>
|
||||
@@ -1355,27 +1582,27 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The rail runs vertically, so what runs out here is height rather than width — at the window's minimum
|
||||
/// the entries have to leave room for each other, which is the same failure the old four-button selector
|
||||
/// was one label away from. It got tighter when the host keys left the keychain screen and became a
|
||||
/// destination of their own, and tighter again with snippets and then the logs, which is why the count
|
||||
/// is asserted rather than left to the fit check: an entry silently dropping off the bottom would still
|
||||
/// pass every other assertion here.
|
||||
/// v5b's redraw changes what this test has to hold. Three button shapes live in the rail now rather
|
||||
/// than one: the switcher's three segments, each a third of the rail's own content width; the six item
|
||||
/// rows below it and the user chip at the foot, both the rail's full content width. A single
|
||||
/// across-the-board width assertion the way the v3 version of this test made one would either be wrong
|
||||
/// for the segments or have to loosen until it caught nothing, so each shape gets its own count and its
|
||||
/// own width now.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Seven now, and it went down rather than up for the first time: SFTP and S3 became fixed tabs in the
|
||||
/// strip, which is where a destination you stay in belongs. The number is asserted in both directions
|
||||
/// for the same reason — an entry that reappeared here would be a route out of the tab the rail lives
|
||||
/// in. See <c>NavRail.axaml</c>.
|
||||
/// The rail runs vertically, so what runs out at the window's minimum is still height — a switcher plus
|
||||
/// six rows plus a user chip have to leave room for each other in the same space the v3 rail's seven
|
||||
/// plain rows did. Both counts are asserted in both directions for the reason the old test's was: an
|
||||
/// entry silently dropping off the bottom would still pass every other assertion here.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheNavRailHoldsSevenDestinationsAtTheWindowsMinimum()
|
||||
public async Task TheNavRailHoldsItsSwitcherSixDestinationsAndTheUserChipAtTheWindowsMinimum()
|
||||
{
|
||||
await LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var rail = new NavRail();
|
||||
var rail = new NavRail { DataContext = shell };
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
rail, LayoutHarness.NavRailWidth, LayoutHarness.ScreenHeight);
|
||||
|
||||
@@ -1383,18 +1610,29 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
{
|
||||
var buttons = rail.GetVisualDescendants().OfType<Button>().ToList();
|
||||
|
||||
buttons.Count.ShouldBe(7, "one per screen the rail reaches, and SFTP and S3 are tabs");
|
||||
var segments = buttons.Where(button => button.Classes.Contains("navseg")).ToList();
|
||||
var rows = buttons.Where(button => button.Classes.Contains("nav")
|
||||
&& !button.Classes.Contains("navseg")).ToList();
|
||||
var chip = buttons.Single(button => button.Classes.Contains("navuser"));
|
||||
|
||||
foreach (var button in buttons)
|
||||
segments.Count.ShouldBe(3, "SSH, SFTP and S3");
|
||||
rows.Count.ShouldBe(
|
||||
6, "the mode-dependent first row, then Hosts, Keys, Pins, Snips and Logs");
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
button.Bounds.Height.ShouldBeGreaterThan(20);
|
||||
segment.Bounds.Height.ShouldBeGreaterThan(20);
|
||||
}
|
||||
|
||||
// 190 wide, less the divider down the rail's right edge, less the 8 of inset on
|
||||
// each side that v2 gives the rows so a filled one reads as a rounded row rather
|
||||
// than as a full-width band. Stated exactly rather than as a lower bound: a button
|
||||
// that stopped filling the row would leave a dead strip beside a destination, which
|
||||
// is precisely the kind of near-miss a bound hides.
|
||||
button.Bounds.Width.ShouldBe(LayoutHarness.NavRailWidth - 1 - 16);
|
||||
foreach (var row in rows.Append(chip))
|
||||
{
|
||||
row.Bounds.Height.ShouldBeGreaterThan(20);
|
||||
|
||||
// 255 wide, less the 1-pixel border down the rail's right edge, less the 14 of
|
||||
// inset v5b's own padding gives each side. Stated exactly rather than as a lower
|
||||
// bound: a row that stopped filling the width would leave a dead strip beside a
|
||||
// destination, which is precisely the kind of near-miss a bound hides.
|
||||
row.Bounds.Width.ShouldBe(LayoutHarness.NavRailWidth - 1 - 28);
|
||||
}
|
||||
|
||||
LayoutHarness.Unreachable(window).ShouldBeEmpty();
|
||||
@@ -1579,8 +1817,21 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
|
||||
/// <summary>Lays the connecting card out in the rectangle the terminal would have had.</summary>
|
||||
/// <remarks>
|
||||
/// The same width and height as a full screen: the card is a sibling of the page area rather than
|
||||
/// something drawn inside one, so what it gets is everything under the tab strip and beside the rail.
|
||||
/// <para>
|
||||
/// ◆ WAVE C's OWN FIX. This used to measure a full screen's width and height, which is roomier than what
|
||||
/// v5b's own session shell actually leaves the card once its padding, tab row, header and sidebar are
|
||||
/// taken out; the gap is closed here rather than merely admitted. <c>ConnectingCard</c> sits in the same
|
||||
/// <c>Grid.Column="0"</c> pane <c>ConnectingPane</c> occupies in <c>MainWindow.axaml</c>'s terminal usage
|
||||
/// — the sidebar is <c>Grid.Column="1"</c>, a true sibling rather than an overlay on top of this one — so
|
||||
/// its real rectangle is <see cref="LayoutHarness.SessionScreenWidth"/>/<see cref="LayoutHarness.SessionScreenHeight"/>,
|
||||
/// not the plain screen budget.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The sidebar is showing whenever this card can be, which is what makes the width unconditional rather
|
||||
/// than a parameter here: <c>ShowsQuickAccessSidebar</c> on the terminal surface is
|
||||
/// <c>SelectedTab is not null</c>, and a connecting card has nothing to show without a selected tab
|
||||
/// either — see the two callers below, both of which select one before calling this.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private Task MeasureConnectingAsync(Action<IReadOnlyList<string>> assert, TerminalTabViewModel tab) =>
|
||||
LayoutHarness.OnTheUiThreadAsync(
|
||||
@@ -1594,7 +1845,7 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
var card = new ConnectingCard { DataContext = shell };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
card, LayoutHarness.ScreenWidth, LayoutHarness.ScreenHeight);
|
||||
card, LayoutHarness.SessionScreenWidth(sidebarVisible: true), LayoutHarness.SessionScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -1609,12 +1860,23 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
|
||||
/// <summary>Lays the host-key decision out in the rectangle it is drawn over.</summary>
|
||||
/// <remarks>
|
||||
/// The vault is the data context, as it is in the window, and the size is a screen's rather than a card's:
|
||||
/// this control carries its own scrim and its own <c>Border.card</c>, so what it is handed is the area the
|
||||
/// overlay covers and the card centres itself inside it. That area is in fact everything under the
|
||||
/// titlebar, which is <see cref="LayoutHarness.TerminalTabsHeight"/> taller than what is used here —
|
||||
/// measuring it at the tighter budget is deliberate, since a card that fits the strip's row too cannot
|
||||
/// stop fitting when the strip is what it is drawn over.
|
||||
/// <para>
|
||||
/// ◆ WAVE C's OWN FINDING, rather than wave C's own fix: this one turned out not to need the narrower
|
||||
/// budget wave B's remark predicted. <c>HostKeyCard</c> is not nested inside either session shell at all
|
||||
/// — <c>MainWindow.axaml</c> draws it as a sibling of the whole unlocked <c>Grid</c> (nav rail and content
|
||||
/// both), the last child before the titlebar/status-bar row, so its own scrim genuinely spans the full
|
||||
/// content area rather than the narrower column either session shell leaves. "Both cover the rectangle
|
||||
/// the terminal would be in" — <c>MainWindowViewModel.IsHostKeyDecisionShowing</c>'s own remark — describes
|
||||
/// the intent the two states share, not this control's actual bounds.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="LayoutHarness.ScreenWidth"/>/<see cref="LayoutHarness.ScreenHeight"/> — the nav rail already
|
||||
/// taken out, the session shell's own padding and sidebar not — stays the right measurement rather than
|
||||
/// the wrong one wave B's remark called it: it is narrower than what this control truly gets (the nav
|
||||
/// rail's own 255 pixels back), so a card proven to fit here is proven to fit the real, wider overlay too.
|
||||
/// <c>Border.card</c>'s own 520-pixel <c>MaxWidth</c> means neither number was ever the risk; what this
|
||||
/// records is that no gap was left for a later wave to close.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private Task MeasureHostKeyAsync(Action<IReadOnlyList<string>> assert) =>
|
||||
LayoutHarness.OnTheUiThreadAsync(
|
||||
@@ -1852,7 +2114,42 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
static (_, _, _, _) => throw new InvalidOperationException("A layout test inserts nothing."));
|
||||
|
||||
/// <summary>Lays the transfers screen out at the width it gets beside the nav rail.</summary>
|
||||
/// <summary>Lays the SFTP usage of <c>TransfersScreen</c> out at the real budget wave B's session shell leaves.</summary>
|
||||
/// <remarks>
|
||||
/// <c>transfers.IsConnected</c> decides the width the same way <c>ShowsQuickAccessSidebar</c> does at
|
||||
/// runtime — see <see cref="LayoutHarness.SessionScreenWidth"/> — so a test that connects before calling
|
||||
/// this measures the tighter, sidebar-narrowed shape without having to say so twice.
|
||||
/// </remarks>
|
||||
private Task MeasureTransfersAsync(Action<IReadOnlyList<string>> assert) =>
|
||||
LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
var screen = new TransfersScreen { DataContext = transfers };
|
||||
|
||||
var window = LayoutHarness.HostAtMinimumSize(
|
||||
screen,
|
||||
LayoutHarness.SessionScreenWidth(transfers.IsConnected),
|
||||
LayoutHarness.SessionScreenHeight);
|
||||
|
||||
try
|
||||
{
|
||||
assert(LayoutHarness.Unreachable(window));
|
||||
}
|
||||
finally
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
},
|
||||
Token);
|
||||
|
||||
/// <summary>Lays the S3 usage of <c>TransfersScreen</c> out at the plain-screen budget it actually gets.</summary>
|
||||
/// <remarks>
|
||||
/// The same control as <see cref="MeasureTransfersAsync"/> measures, at a different width and height: S3
|
||||
/// is "deliberately not given the session shell" — see <c>MainWindow.axaml</c>'s own remark on why — so it
|
||||
/// is measured at <see cref="LayoutHarness.ScreenWidth"/>/<see cref="LayoutHarness.ScreenHeight"/> instead,
|
||||
/// the same budget every other full-bleed page gets.
|
||||
/// </remarks>
|
||||
private Task MeasureBucketsAsync(Action<IReadOnlyList<string>> assert) =>
|
||||
LayoutHarness.OnTheUiThreadAsync(
|
||||
() =>
|
||||
{
|
||||
@@ -1896,8 +2193,9 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
/// <summary>Lays the vaults screen out at the width it gets once the nav rail has taken its column.</summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Its right-hand column is the narrowest measured here: the window's minimum is 1016, the nav rail
|
||||
/// takes 190 and the vault list 268, leaving 558 for everything above.
|
||||
/// Its right-hand column is the narrowest measured here: the window's minimum is 1081, the nav rail
|
||||
/// takes 255 and the vault list 268, leaving 558 for everything above — the same 558 as before v5b
|
||||
/// widened the rail, because the minimum grew by exactly what the rail did.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Every list is seeded, and seeded with the long rows rather than the convenient ones — see
|
||||
|
||||
Reference in New Issue
Block a user