Public Access
Merge pull request 'Name a quick-connect result's vault, and search on it' (#13) from claude/search-items-vault-display-865eb0 into main
Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
@@ -236,6 +236,51 @@ public sealed class QuickConnectTests : IAsyncLifetime
|
||||
});
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The palette searches every vault the session holds a key for, so a row has to say which one it came
|
||||
/// out of. Two machines a team and a person both call <c>prod-db</c> are otherwise two identical rows,
|
||||
/// and Enter takes whichever the ranking happened to put first.
|
||||
/// <para>
|
||||
/// Typed into rather than read off the unfiltered list, because the shared vault's host sorts last — the
|
||||
/// active vault's rows come first — and the list virtualises, so the row this is about might never be
|
||||
/// realised. Narrowing to it also proves the search reaches past the active vault at all.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AResultSaysWhichVaultItCameOutOf()
|
||||
{
|
||||
await SeedSharedHostAsync();
|
||||
|
||||
await OnThePaletteAsync((palette, window) =>
|
||||
{
|
||||
shell.SearchText = "prod-db";
|
||||
Relayout(window);
|
||||
|
||||
var found = shell.SearchResults.ShouldHaveSingleItem();
|
||||
found.VaultId.ShouldNotBe(session.ActiveVaultId, "the palette reaches past the active vault");
|
||||
|
||||
VisibleTexts(RowFor(palette, found)).ShouldContain("PLATFORM SECRETS", StringComparer.Ordinal);
|
||||
});
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The other half of the rule, and the reason the name is a badge rather than a column: a vault named on
|
||||
/// every row of a session that has only one is the same fact repeated, which is noise rather than a
|
||||
/// reading. <c>HostRowViewModel.VaultBadge</c> is empty there, and an empty line has to collapse rather
|
||||
/// than leave a gap above the kind word.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public async Task AResultNamesNoVaultWhenThereIsOnlyOneToBeIn()
|
||||
{
|
||||
await OnThePaletteAsync((palette, _) =>
|
||||
{
|
||||
var first = shell.SearchResults[0];
|
||||
|
||||
first.HasVaultBadge.ShouldBeFalse("this fixture's session holds the personal vault alone");
|
||||
VisibleTexts(RowFor(palette, first)).ShouldNotContain("PERSONAL", StringComparer.Ordinal);
|
||||
});
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// The palette is a box somebody is expected to start typing into, and for a while it was not: the window
|
||||
/// focused it from the view model's <c>PropertyChanged</c>, which runs before the binding that reveals the
|
||||
@@ -307,6 +352,25 @@ public sealed class QuickConnectTests : IAsyncLifetime
|
||||
.OfType<ListBoxItem>()
|
||||
.First(item => ReferenceEquals(item.DataContext, host));
|
||||
|
||||
/// <summary>What one row actually draws, in order, ignoring the lines that collapsed.</summary>
|
||||
private static List<string> VisibleTexts(Visual row) =>
|
||||
row.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.Where(text => text.IsEffectivelyVisible)
|
||||
.Select(text => text.Text ?? string.Empty)
|
||||
.ToList();
|
||||
|
||||
/// <summary>Runs the layout pass the application's dispatcher would run after the list changed.</summary>
|
||||
/// <remarks>
|
||||
/// Without it the new rows are in the collection but not in the visual tree, so <see cref="RowFor"/>
|
||||
/// finds nothing to look at.
|
||||
/// </remarks>
|
||||
private static void Relayout(Window window)
|
||||
{
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
window.UpdateLayout();
|
||||
}
|
||||
|
||||
private static Point Centre(Visual control, Visual window) =>
|
||||
control.TranslatePoint(new Point(control.Bounds.Width / 2, control.Bounds.Height / 2), window)
|
||||
?? throw new InvalidOperationException("the control is not in this window's tree");
|
||||
@@ -325,4 +389,37 @@ public sealed class QuickConnectTests : IAsyncLifetime
|
||||
|
||||
await vault.LoadAsync(Token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a second, shared vault to the fixture's session and files one host into it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Straight into the session rather than through <c>VaultsViewModel</c>, which is the technique the
|
||||
/// settings pages' suite uses and for the same reason: creating a vault needs a connection, and this
|
||||
/// shell has none. The key is generated on this machine either way, so what the session ends up holding
|
||||
/// is the same thing a real creation leaves behind — see <see cref="StubTeamServer"/>.
|
||||
/// </remarks>
|
||||
private async Task SeedSharedHostAsync()
|
||||
{
|
||||
using var teamServer = new StubTeamServer();
|
||||
|
||||
var shared = await session.CreateTeamVaultAsync(
|
||||
teamServer.Teams, StubTeamServer.SharedTeamId, "Platform secrets", Token);
|
||||
|
||||
await vault.LoadAsync(Token);
|
||||
|
||||
vault.SelectedTargetVault =
|
||||
vault.TargetVaults.Single(choice => choice.VaultId == shared.VaultId);
|
||||
|
||||
vault.NewHostCommand.Execute(null);
|
||||
vault.EditorLabel = "prod-db";
|
||||
vault.EditorHostname = "db.internal";
|
||||
vault.EditorUsername = "deploy";
|
||||
|
||||
await vault.SaveHostCommand.ExecuteAsync(null);
|
||||
|
||||
vault.IsEditing.ShouldBeFalse(vault.Status);
|
||||
|
||||
await vault.LoadAsync(Token);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user