Public Access
Choose more than one host card on the desktop, the way the phone already can
The chosen-hosts set has been in VaultViewModel since the phone's connect card became a contextual action bar: a set of entity ids, a tick on the row, and seven things that can be done to it. Only one head could fill it. The desktop's grid answered a press with one selection — the card the drawer, CONNECT and the context menu are about — so filing eleven imported machines under a heading was eleven drags, and clearing out a vault was eleven rounds of the deletion question. So the pointer gets three ways into the same set. Ctrl-clicks a card to tick it, Shift-clicks to tick the run between the anchor and the card, and drags a band out over the space between and below the cards to tick everything it touches. Esc, CLEAR, a plain click on a card and a click on the empty space each drop it, and Ctrl+A takes every card being drawn — VisibleHosts, so with something in the find box that is the ones on screen and not the ones it is hiding, which is the version of that shortcut whose result can be checked before Delete is pressed. TWO SELECTIONS ON ONE SCREEN, AND KEEPING THEM FROM DISAGREEING IS MOST OF THE CHANGE. Ctrl and Shift are answered on the tunnel and marked handled, so the ListBox never moves its own mark onto the card: a Ctrl-click that also selected would light the card it had just unticked and open the drawer on a machine somebody is removing from a set. A plain press drops the set unless it lands on a ticked card, and that case is deferred to the release, because the press may be the start of a drag of all of it. After any ordinary click exactly one card is in play, which is what makes every command on the screen unambiguous again. The context menu is where the seven live, and it is one markup with two halves gated on IsChoosingHosts. Connect, Browse files and Edit… are drawn only for a single ticked host, as the phone's sheet collapses them and for the same reason; the other four read better for a count. A right click on a card outside the set drops the set first, so a Delete… about the card under the pointer can never be offered while six sit ticked behind the menu — the same rule OnContextRequested has always enforced for the selection, reached from the other direction. No bar of buttons: the phone raises one because it has no other way to hold seven entries, and a strip repeating a menu that already exists would be a second home for the wording that matters most. What the desktop gains instead is a count beside the HOSTS heading, CLEAR, and a sentence saying where the actions are. A drag that starts on a ticked card carries every ticked card. The payload is a list rather than a row now, and a drop of more than one goes through FileChosenHostsUnder, which makes the refusals once — an open editor, and a group belonging to one keychain — and reports a count instead of forty status lines. Moving whichever card the pointer happened to be holding and leaving the other five where they are is a gesture that quietly does a fraction of what it looks like it does, and the five left behind look filed. The three panels the set's actions raise had never been drawn in a window: the vault picker with its key question, the group picker, and the deletion question. All three sit above the grid rather than over it, which is the arrangement the GROUPS section and the phone's list already use and for the reason written there — the ticked cards are the information the question exists to give, so the grid shortens instead. A DEFECT FOUND BEHIND IT, AND IT WAS ALREADY LIVE ON THE PHONE. The deletion question names a count and the run that answers it reads the set again, and nothing kept the two the same set: the panel is deliberately above a live list, so one more tick between "Delete these 6 hosts?" and pressing DELETE deleted seven, with the seventh named in nothing the user had read. It needed a deliberate act on a phone and a second's work with a band, which is what turned it up. VaultViewModel now remembers which hosts the question was asked about and drops the question when the set stops being them — the question rather than the set, because what somebody has just chosen is what they meant. It also covers the case nobody performs: a colleague's deletion arriving mid-question and shrinking the set under it. VERIFIED. 354 tests in App.Tests and 111 in App.Layout.Tests, build clean, no new warnings. Six gesture tests drive real pointer and key input through the headless window — the modifier click and what it must not do to the selection, the run and its re-measurement from the anchor, the band and the click that drops the set, Ctrl+A under a filter, and the menu's two halves — plus a DragOver carrying two hosts. Four layout tests measure the strip and the three panels at the window's minimum; the vault panel binds a key to its host first, or it would measure the short shape and certify the tall one. Two flow tests cover the multi-drop's write and its refusal, and the deletion question dropping itself. manual-checks gains 7.6a for dragging a set, which no test can see for the reason 7.6 gives, and 7.7a for the gestures — the rectangle actually being painted and the tick and the fill being legible together are the two things the harness cannot look at.
This commit is contained in:
@@ -630,6 +630,115 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
await MeasureHostsAsync(faults => faults.ShouldBeEmpty("with the group move panel up"));
|
||||
}
|
||||
|
||||
// ---- ◆ The hosts screen with a set of cards ticked ----
|
||||
//
|
||||
// Ctrl, Shift and a band put the phone's chosen-hosts set on this screen — see HostsScreen.axaml.cs — and
|
||||
// with it come one strip and three panels that had never been drawn in a window. All four sit between the
|
||||
// HOSTS heading and the grid, so every one of them shortens the grid rather than overflowing it; that is
|
||||
// the property these measure. The gestures themselves are HostGridTests'.
|
||||
|
||||
/// <remarks>
|
||||
/// The strip: a count, CLEAR, and the sentence saying the actions are on the menu — squeezed between the
|
||||
/// heading and the grid's own count on the same row. It is the one thing on this screen whose width is
|
||||
/// set by nothing but its text, so what is really being measured is that the sentence trims instead of
|
||||
/// running out over the number at the far end. Measured with the drawer open, which is the width at which
|
||||
/// it does not fit and has to.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheHostsScreenFitsWhileCardsAreTicked()
|
||||
{
|
||||
vault.ChooseHostCommand.Execute(vault.Hosts[0]);
|
||||
vault.ToggleHostChoiceCommand.Execute(vault.Hosts[1]);
|
||||
|
||||
vault.IsChoosingHosts.ShouldBeTrue("the strip is only drawn while something is ticked");
|
||||
|
||||
vault.OpenHostPaneCommand.Execute(vault.Hosts[0]);
|
||||
vault.IsDrawerOpen.ShouldBeTrue("the drawer is what takes the width away");
|
||||
|
||||
await MeasureHostsAsync(faults => faults.ShouldBeEmpty("with two cards ticked"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The tallest of the three panels: the vault picker with the key question under it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A heading, a picker, a wrapping paragraph naming everything that travels and everything that does not,
|
||||
/// a tick with a second wrapping sentence beside it, and two buttons — all above the group cards and the
|
||||
/// grid, which still have to fit under it.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The host is given a key first, because the tick is drawn only for a move of exactly one host that has
|
||||
/// something to bring; without that this would measure the short shape and say the long one fits. The
|
||||
/// panel is opened by hand rather than through <c>MoveChosenHostsToVault</c> for the reason the group
|
||||
/// move test gives: this fixture's account holds one vault, and the command declines rather than open a
|
||||
/// picker with nothing in it.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheHostsScreenFitsWithTheChosenHostsVaultPanelOpen()
|
||||
{
|
||||
vault.SelectedHost = vault.Hosts[0];
|
||||
vault.EditSelectedHostCommand.Execute(null);
|
||||
|
||||
vault.EditorSelectedAuthentication = vault.EditorAuthenticationChoices
|
||||
.First(choice => choice.Kind is AuthenticationKind.SshKey);
|
||||
|
||||
await vault.SaveHostCommand.ExecuteAsync(null);
|
||||
|
||||
vault.ChooseHostCommand.Execute(vault.Hosts[0]);
|
||||
|
||||
vault.ChosenHostVaultChoices.Add(
|
||||
new VaultChoiceViewModel(Guid.CreateVersion7(), "Platform Engineering secrets", false));
|
||||
|
||||
vault.SelectedChosenHostVault = vault.ChosenHostVaultChoices[0];
|
||||
vault.IsSendingChosenHostsToAVault = true;
|
||||
|
||||
vault.HasAChosenBindingToBring
|
||||
.ShouldBeTrue("the key question is the part of this panel worth measuring");
|
||||
|
||||
await MeasureHostsAsync(faults => faults.ShouldBeEmpty("with the set's vault panel up"));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The group picker over the set, which is the same panel the phone draws and the same write a set
|
||||
/// dragged onto a group card makes. Shorter than the vault panel above and drawn in the same place, so
|
||||
/// what this adds is the picker being filled from one keychain's groups rather than from nothing.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheHostsScreenFitsWithTheChosenHostsGroupPanelOpen()
|
||||
{
|
||||
await SeedGroupsAsync(3);
|
||||
|
||||
vault.ChooseHostCommand.Execute(vault.Hosts[0]);
|
||||
vault.ToggleHostChoiceCommand.Execute(vault.Hosts[1]);
|
||||
|
||||
vault.RegroupChosenHostsCommand.Execute(null);
|
||||
|
||||
vault.IsRegroupingChosenHosts.ShouldBeTrue(vault.Status);
|
||||
vault.ChosenHostGroupChoices.Count.ShouldBeGreaterThan(1, "no group, and the three seeded ones");
|
||||
|
||||
await MeasureHostsAsync(faults => faults.ShouldBeEmpty("with the set's group panel up"));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// One question for the whole set, drawn by the same card the group deletion above uses. The count is
|
||||
/// what makes it a confirmation somebody reads rather than one they press past, and the consequence line
|
||||
/// wraps — which is the part a narrower column would push out of the window.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheHostsScreenFitsWhileTheChosenHostsAreBeingDeleted()
|
||||
{
|
||||
vault.ChooseHostCommand.Execute(vault.Hosts[0]);
|
||||
vault.ToggleHostChoiceCommand.Execute(vault.Hosts[1]);
|
||||
|
||||
vault.DeleteChosenHostsCommand.Execute(null);
|
||||
|
||||
vault.IsConfirmingChosenHostDeletion.ShouldBeTrue("the question has to be up for this to measure it");
|
||||
|
||||
await MeasureHostsAsync(faults => faults.ShouldBeEmpty("with the set's deletion question up"));
|
||||
}
|
||||
|
||||
// ---- The vault screen ----
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user