Public Access
Merge branch 'claude/main-page-group-hierarchy-3a3210'
This commit is contained in:
@@ -233,7 +233,8 @@ public sealed class HostGridTests : IAsyncLifetime
|
||||
vault.SelectedGroup = production;
|
||||
|
||||
vault.GroupFilter.ShouldBeNull("one press selects a group and does not open it");
|
||||
vault.VisibleHosts.Count.ShouldBe(2, "so the grid still holds the hosts outside it");
|
||||
vault.VisibleHosts.Select(row => row.Label)
|
||||
.ShouldBe(["stage-web"], "so the grid is still the outermost level, and prod-db is inside a group");
|
||||
vault.GroupTarget.ShouldBeSameAs(production, "what EDIT and DELETE act on");
|
||||
vault.ShowsGroupActions.ShouldBeTrue();
|
||||
|
||||
@@ -252,7 +253,8 @@ public sealed class HostGridTests : IAsyncLifetime
|
||||
// same job and went with the change.
|
||||
vault.OpenGroupCommand.Execute(vault.GroupTrail[0].Group);
|
||||
|
||||
vault.VisibleHosts.Count.ShouldBe(2, "ALL HOSTS brings back the hosts outside the group");
|
||||
vault.VisibleHosts.Select(row => row.Label)
|
||||
.ShouldBe(["stage-web"], "ALL HOSTS is the outermost level, not every host in the keychain");
|
||||
vault.GroupTarget.ShouldBeNull("and nothing is aimed at once no group is open or selected");
|
||||
vault.ShowsGroupActions.ShouldBeFalse("a pair of buttons with no subject is hidden rather than shown");
|
||||
}
|
||||
|
||||
@@ -344,7 +344,10 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
/// that spans the width is as inside it as a card is.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Measured with every host filed, which is the shape that used to produce the most headings.
|
||||
/// Measured with every host filed and that group open, which is the shape that used to produce the most
|
||||
/// headings. Open, because the grid holds one level of the tree — a host inside a group is drawn inside
|
||||
/// that group and nowhere else — so measuring at the outermost level would be measuring an empty grid.
|
||||
/// See <c>VaultViewModel.Matches</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
@@ -358,6 +361,8 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
new HostGroupMove(host, vault.Groups[0].EntityId));
|
||||
}
|
||||
|
||||
vault.OpenGroupCommand.Execute(vault.Groups[0]);
|
||||
|
||||
await OnTheHostsScreenAsync((screen, _) =>
|
||||
{
|
||||
var rows = screen.HostGrid
|
||||
@@ -370,7 +375,8 @@ public sealed class ScreenLayoutTests : IAsyncLifetime
|
||||
rows.ShouldAllBe(row => row is HostRowViewModel);
|
||||
});
|
||||
|
||||
await MeasureHostsAsync(faults => faults.ShouldBeEmpty("with three groups and every host filed"));
|
||||
await MeasureHostsAsync(
|
||||
faults => faults.ShouldBeEmpty("with every host filed and the group holding them open"));
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
||||
@@ -3584,12 +3584,22 @@ public sealed class ShellFlowTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// What dragging a host card onto a group card does. It is the same write the editor makes — one field
|
||||
/// of the host, pushed straight away — reached without opening a form, because filing thirty imported
|
||||
/// machines through the editor is thirty rounds of open, pick, save.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>The card goes into the group and off the level it was dragged from</b>, which is the whole of what
|
||||
/// a drop looks like on a grid that holds one level of the tree — the host is inside the card it was
|
||||
/// dropped on now, and that is where it is drawn. It used to stay put and gain a chip. The selection
|
||||
/// goes with it rather than being restored onto something nobody can see: Connect, Edit and Delete all
|
||||
/// read that property, and none of them should be aimed at a card that has left the screen. See
|
||||
/// <c>VaultViewModel.Matches</c> and <c>RebuildVisibleHosts</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task MovingAHostToAGroup_FilesItAndLeavesItSelected()
|
||||
public async Task MovingAHostToAGroup_FilesItAndTakesItOffTheLevelItCameFrom()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
var vault = shell.Vault!;
|
||||
@@ -3603,23 +3613,166 @@ public sealed class ShellFlowTests : IAsyncLifetime
|
||||
await vault.MoveHostToGroupCommand.ExecuteAsync(new HostGroupMove(host, group));
|
||||
|
||||
vault.Hosts.Single().Host.GroupId.ShouldBe(group);
|
||||
vault.SelectedHost.ShouldNotBeNull().EntityId.ShouldBe(host.EntityId, "the reload replaces every row");
|
||||
vault.VisibleHosts.ShouldBeEmpty("the grid is the outermost level and the host is inside a group");
|
||||
vault.SelectedHost.ShouldBeNull("nothing on screen is it any more");
|
||||
|
||||
// Under the group's own heading now, which is what the phone's list draws.
|
||||
// Under the group's own heading now, which is what the phone's list draws — that list is the whole
|
||||
// tree flattened, so the host is still in it.
|
||||
vault.SidebarRows.OfType<SidebarGroupHeader>()
|
||||
.Single(header => header.GroupId == group)
|
||||
.Count.ShouldBe(1);
|
||||
|
||||
// And the name on the card, which is what the desktop's grid draws instead of that heading — the one
|
||||
// thing on screen that changes where the host was dropped rather than where it came from.
|
||||
// The name is on the card, which is what the desktop's grid draws instead of that heading, and what
|
||||
// says which group a searched-up card came out of.
|
||||
vault.Hosts.Single().GroupLabel.ShouldBe("production");
|
||||
vault.Hosts.Single().HasGroup.ShouldBeTrue();
|
||||
|
||||
// Opening the group is where it went, and the way to it.
|
||||
vault.OpenGroupCommand.Execute(vault.Groups.Single());
|
||||
|
||||
vault.VisibleHosts.ShouldHaveSingleItem().Label.ShouldBe("prod-db");
|
||||
|
||||
// And back out again, which is what the host's own editor is for now that the drop has one target.
|
||||
vault.OpenGroupCommand.Execute(null);
|
||||
|
||||
await vault.MoveHostToGroupCommand.ExecuteAsync(new HostGroupMove(vault.Hosts.Single(), null));
|
||||
|
||||
vault.Hosts.Single().Host.GroupId.ShouldBeNull();
|
||||
vault.Hosts.Single().HasGroup.ShouldBeFalse("and the chip goes with it");
|
||||
|
||||
// Coming out of a group is the direction that lands the host back on this level, so here the
|
||||
// selection does survive the move.
|
||||
vault.VisibleHosts.ShouldHaveSingleItem().Label.ShouldBe("prod-db");
|
||||
vault.SelectedHost.ShouldNotBeNull().Label.ShouldBe("prod-db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The grid holds one level of the tree, the way a directory pane holds one directory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A host filed under a group is inside that group and is not also on the screen the group's card sits
|
||||
/// on. While it was both, opening a group could only ever take hosts away — the level above already had
|
||||
/// all of them — and the cards were headings rather than places. The phone's list is the deliberate
|
||||
/// exception and is asserted here beside it: it draws the whole tree flat under headings, because it has
|
||||
/// no cards to open and nowhere to open one into.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AHostFiledUnderAGroup_IsDrawnInsideItAndNotAtTheLevelAbove()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
var vault = shell.Vault!;
|
||||
|
||||
await AddHostAsync(vault, "prod-db");
|
||||
await AddHostAsync(vault, "stage-web");
|
||||
await AddGroupAsync(vault, "production");
|
||||
await FileAsync(vault, "prod-db", "production");
|
||||
|
||||
vault.VisibleHosts.Select(row => row.Label)
|
||||
.ShouldBe(["stage-web"], "the outermost level is what nothing has been filed into");
|
||||
|
||||
vault.SidebarRows.OfType<HostRowViewModel>().Select(row => row.Label)
|
||||
.ShouldBe(["prod-db", "stage-web"], "the phone's list is the whole tree flattened");
|
||||
|
||||
vault.OpenGroupCommand.Execute(vault.Groups.Single());
|
||||
|
||||
vault.VisibleHosts.Select(row => row.Label).ShouldBe(["prod-db"]);
|
||||
|
||||
vault.OpenGroupCommand.Execute(null);
|
||||
|
||||
vault.VisibleHosts.Select(row => row.Label).ShouldBe(["stage-web"]);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The one thing on the screen that crosses a group boundary, and it has to be one. A search that looked
|
||||
/// only at the level it was typed on would answer "no host matches that" about a machine this keychain
|
||||
/// has got — and finding a machine without first remembering where it was filed is most of what the box
|
||||
/// is for. Typed at the outermost level it reaches everything; typed inside a group it reaches that
|
||||
/// group and what is under it, which is the same rule read from where you are standing.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task TheFindBox_SearchesInsideTheGroupsRatherThanOnlyTheLevelOnScreen()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
var vault = shell.Vault!;
|
||||
|
||||
await AddHostAsync(vault, "prod-db");
|
||||
await AddHostAsync(vault, "stage-web");
|
||||
await AddGroupAsync(vault, "estate");
|
||||
await AddGroupAsync(vault, "production");
|
||||
await SetGroupParentAsync(vault, "production", "estate");
|
||||
await FileAsync(vault, "prod-db", "production");
|
||||
|
||||
vault.VisibleHosts.ShouldNotContain(row => string.Equals(row.Label, "prod-db", StringComparison.Ordinal));
|
||||
|
||||
vault.HostFilter = "prod-db";
|
||||
|
||||
vault.VisibleHosts.ShouldHaveSingleItem().Label
|
||||
.ShouldBe("prod-db", "two levels down, and the box reaches it");
|
||||
|
||||
// And inside a group it is that group's subtree: estate holds production, which holds the host.
|
||||
vault.OpenGroupCommand.Execute(
|
||||
vault.Groups.Single(row => string.Equals(row.Label, "estate", StringComparison.Ordinal)));
|
||||
|
||||
vault.VisibleHosts.ShouldHaveSingleItem().Label.ShouldBe("prod-db");
|
||||
|
||||
vault.HostFilter = "stage";
|
||||
|
||||
vault.VisibleHosts.ShouldBeEmpty("stage-web is outside estate, so this search does not reach it");
|
||||
vault.NoVisibleHostsMessage.ShouldContain("ALL HOSTS", Case.Sensitive, "and it says how to widen it");
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// A grid with no cards in it has to say why, and "they are all filed away" is a different sentence from
|
||||
/// "there are none" and from "nothing matches what you typed". It is the answer the level-at-a-time grid
|
||||
/// made reachable: before it, a keychain with hosts in it always drew some.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AKeychainWhoseHostsAreAllFiled_SaysSoRatherThanLookingEmpty()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
var vault = shell.Vault!;
|
||||
|
||||
await AddHostAsync(vault, "prod-db");
|
||||
await AddGroupAsync(vault, "production");
|
||||
await FileAsync(vault, "prod-db", "production");
|
||||
|
||||
vault.HasVisibleHosts.ShouldBeFalse();
|
||||
vault.NoVisibleHostsMessage.ShouldContain("filed under a group");
|
||||
vault.NoVisibleHostsMessage.ShouldNotContain(
|
||||
"No hosts yet", Case.Sensitive, "telling somebody with hosts to add their first one answers nothing");
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// Where a new thing lands, now that the screen is somewhere rather than everywhere. A host created
|
||||
/// inside a group and filed under none would vanish from the screen it was created on, which is the
|
||||
/// papercut that comes free with a grid holding one level — so the editor opens on the group the user
|
||||
/// is standing in, and the picker shows it before anything is saved.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task ANewHostOrGroupStartedInsideAGroup_IsMadeInsideIt()
|
||||
{
|
||||
await UnlockedAsync();
|
||||
var vault = shell.Vault!;
|
||||
|
||||
await AddGroupAsync(vault, "production");
|
||||
|
||||
vault.OpenGroupCommand.Execute(vault.Groups.Single());
|
||||
|
||||
vault.NewHostCommand.Execute(null);
|
||||
|
||||
vault.EditorSelectedGroup.ShouldNotBeNull().Label.ShouldBe("production");
|
||||
|
||||
vault.EditorLabel = "prod-db";
|
||||
vault.EditorHostname = "db.internal";
|
||||
await vault.SaveHostCommand.ExecuteAsync(null);
|
||||
|
||||
vault.VisibleHosts.ShouldHaveSingleItem().Label
|
||||
.ShouldBe("prod-db", "so it is on the screen it was made on");
|
||||
|
||||
vault.NewGroupCommand.Execute(null);
|
||||
|
||||
vault.GroupEditorSelectedParent.ShouldNotBeNull().Label
|
||||
.ShouldBe("production", "+ NEW GROUP inside a group makes one inside it");
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
||||
Reference in New Issue
Block a user