[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
501
components/vault/VaultHostListSection.test.tsx
Normal file
501
components/vault/VaultHostListSection.test.tsx
Normal file
@@ -0,0 +1,501 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import {
|
||||
CheckSquare,
|
||||
ClipboardCopy,
|
||||
Clock,
|
||||
Copy,
|
||||
Edit2,
|
||||
FileSymlink,
|
||||
FolderPlus,
|
||||
FolderTree,
|
||||
LayoutGrid,
|
||||
Pin,
|
||||
Plug,
|
||||
Square,
|
||||
Star,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
|
||||
import { getEffectiveHostDistro, sanitizeHost } from "../../domain/host.ts";
|
||||
import type { GroupNode, Host } from "../../types.ts";
|
||||
import { DistroAvatar } from "../DistroAvatar.tsx";
|
||||
import { HostTreeView } from "../HostTreeView.tsx";
|
||||
import { Badge } from "../ui/badge.tsx";
|
||||
import { Button } from "../ui/button.tsx";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "../ui/context-menu.tsx";
|
||||
import { cn } from "../../lib/utils.ts";
|
||||
import {
|
||||
getVaultTreeAutoExpandKey,
|
||||
VaultHostListSection,
|
||||
} from "./VaultHostListSection.tsx";
|
||||
|
||||
Object.defineProperty(globalThis, "localStorage", {
|
||||
configurable: true,
|
||||
value: {
|
||||
getItem: () => null,
|
||||
setItem: () => {},
|
||||
removeItem: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const makeHost = (id: string, label: string): Host => ({
|
||||
id,
|
||||
label,
|
||||
hostname: "router.example.com",
|
||||
username: "netops",
|
||||
port: 22,
|
||||
os: "linux",
|
||||
tags: [],
|
||||
notes: "Maintenance notes",
|
||||
createdAt: 1,
|
||||
});
|
||||
|
||||
const mainHost = makeHost("main-host", "Main Router");
|
||||
const pinnedHost = makeHost("pinned-host", "Pinned Router");
|
||||
const recentHost = makeHost("recent-host", "Recent Router");
|
||||
const groupedHost = makeHost("grouped-host", "Grouped Router");
|
||||
|
||||
const group: GroupNode = {
|
||||
name: "Production",
|
||||
path: "production",
|
||||
children: {},
|
||||
hosts: [mainHost],
|
||||
totalHostCount: 1,
|
||||
};
|
||||
|
||||
const noop = () => undefined;
|
||||
|
||||
test("tree auto-expansion covers both text and tag filters", () => {
|
||||
assert.equal(getVaultTreeAutoExpandKey("", []), undefined);
|
||||
assert.ok(getVaultTreeAutoExpandKey("router", []));
|
||||
assert.ok(getVaultTreeAutoExpandKey("", ["production"]));
|
||||
assert.equal(
|
||||
getVaultTreeAutoExpandKey("router", ["production", "linux"]),
|
||||
getVaultTreeAutoExpandKey("router", ["linux", "production"]),
|
||||
);
|
||||
});
|
||||
|
||||
type RenderHostListOptions = {
|
||||
displayedGroups?: GroupNode[];
|
||||
displayedHosts?: Host[];
|
||||
groupedDisplayHosts?: Array<{ name: string; hosts: Host[] }>;
|
||||
isMultiSelectMode?: boolean;
|
||||
pinnedHosts?: Host[];
|
||||
pinnedRecentIds?: Set<string>;
|
||||
recentHosts?: Host[];
|
||||
showRecentHosts?: boolean;
|
||||
selectedGroupPaths?: Set<string>;
|
||||
selectedHostIds?: Set<string>;
|
||||
sortMode?: string;
|
||||
treeViewGroupTree?: GroupNode[];
|
||||
treeViewHosts?: Host[];
|
||||
viewMode: "list" | "grid" | "tree";
|
||||
visibleDisplayedHosts?: Host[];
|
||||
};
|
||||
|
||||
const renderHostList = ({
|
||||
displayedGroups = [group],
|
||||
displayedHosts = [mainHost],
|
||||
groupedDisplayHosts,
|
||||
isMultiSelectMode = false,
|
||||
pinnedHosts = [],
|
||||
pinnedRecentIds = new Set<string>(),
|
||||
recentHosts = [],
|
||||
showRecentHosts = false,
|
||||
selectedGroupPaths = new Set<string>(),
|
||||
selectedHostIds = new Set<string>(),
|
||||
sortMode = "az",
|
||||
treeViewGroupTree = [],
|
||||
treeViewHosts = [],
|
||||
viewMode,
|
||||
visibleDisplayedHosts = [mainHost],
|
||||
}: RenderHostListOptions) => renderToStaticMarkup(
|
||||
<VaultHostListSection
|
||||
ctx={{
|
||||
Badge,
|
||||
Boolean,
|
||||
Button,
|
||||
cancelInlineGroupEdit: noop,
|
||||
CheckSquare,
|
||||
ClipboardCopy,
|
||||
Clock,
|
||||
cn,
|
||||
commitInlineGroupRename: noop,
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
Copy,
|
||||
displayedGroups,
|
||||
displayedHosts,
|
||||
DistroAvatar,
|
||||
Edit2,
|
||||
FileSymlink,
|
||||
FolderPlus,
|
||||
FolderTree,
|
||||
getDropTargetClasses: () => "",
|
||||
getEffectiveHostDistro,
|
||||
groupConfigs: [],
|
||||
groupedDisplayHosts,
|
||||
handleCopyCredentials: noop,
|
||||
handleCopyHostname: noop,
|
||||
handleDuplicateHost: noop,
|
||||
handleEditGroupConfig: noop,
|
||||
handleEditHost: noop,
|
||||
handleHostConnect: noop,
|
||||
handleUnmanageGroup: noop,
|
||||
hasHostsSidePanel: false,
|
||||
hostListScrollRef: React.createRef<HTMLDivElement>(),
|
||||
HostTreeView,
|
||||
isHostsSectionActive: true,
|
||||
isMultiSelectMode,
|
||||
lastPinnedId: null,
|
||||
LayoutGrid,
|
||||
managedGroupPaths: new Set<string>(),
|
||||
moveGroup: noop,
|
||||
moveHostToGroup: noop,
|
||||
onDeleteHost: noop,
|
||||
Pin,
|
||||
pinnedHosts,
|
||||
pinnedRecentIds,
|
||||
Plug,
|
||||
recentHosts,
|
||||
reorderGroup: noop,
|
||||
reorderHost: noop,
|
||||
sanitizeHost,
|
||||
selectedGroupPath: null,
|
||||
selectedGroupPaths,
|
||||
selectedHostIds,
|
||||
sessionCount: 0,
|
||||
setDeleteTargetPath: noop,
|
||||
setDragOverDropTarget: noop,
|
||||
setGroupDragOverDropTarget: noop,
|
||||
setIsDeleteGroupOpen: noop,
|
||||
setIsNewFolderOpen: noop,
|
||||
setLastPinnedId: noop,
|
||||
setNewFolderName: noop,
|
||||
setSelectedGroupPath: noop,
|
||||
setTargetParentPath: noop,
|
||||
shouldHideEmptyRootHostsSection: false,
|
||||
showRecentHosts,
|
||||
sortMode,
|
||||
splitViewGridStyle: undefined,
|
||||
Square,
|
||||
Star,
|
||||
startInlineDeleteGroup: noop,
|
||||
startInlineNewGroup: noop,
|
||||
startInlineRenameGroup: noop,
|
||||
t: (key: string) => key,
|
||||
toggleGroupSelection: noop,
|
||||
toggleHostPinned: noop,
|
||||
toggleHostSelection: noop,
|
||||
Trash2,
|
||||
treeExpandedState: {
|
||||
expandedPaths: new Set<string>(),
|
||||
togglePath: noop,
|
||||
expandAll: noop,
|
||||
collapseAll: noop,
|
||||
},
|
||||
treeViewGroupTree,
|
||||
treeViewHosts,
|
||||
viewMode,
|
||||
visibleDisplayedHosts,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const editButtonIndexForHost = (markup: string, hostId: string) =>
|
||||
markup.indexOf(`data-vault-host-edit-button="${hostId}"`);
|
||||
|
||||
const editButtonIndexForGroup = (markup: string, groupPath: string) =>
|
||||
markup.indexOf(`data-vault-group-edit-button="${groupPath}"`);
|
||||
|
||||
const assertListHostPlacement = (markup: string, host: Host) => {
|
||||
const listLabelIndex = markup.indexOf(host.label);
|
||||
const listEditIndex = editButtonIndexForHost(markup, host.id);
|
||||
const listNotesIndex = markup.indexOf('aria-label="Host notes"', listLabelIndex);
|
||||
|
||||
assert.ok(listLabelIndex >= 0);
|
||||
assert.ok(listEditIndex > listLabelIndex);
|
||||
assert.ok(listNotesIndex > listEditIndex);
|
||||
};
|
||||
|
||||
const assertGridHostPlacement = (markup: string, host: Host) => {
|
||||
const gridLabelIndex = markup.indexOf(host.label);
|
||||
const gridNotesIndex = markup.indexOf('aria-label="Host notes"', gridLabelIndex);
|
||||
const gridEditIndex = editButtonIndexForHost(markup, host.id);
|
||||
|
||||
assert.ok(gridLabelIndex >= 0);
|
||||
assert.ok(gridNotesIndex > gridLabelIndex);
|
||||
assert.ok(gridEditIndex > gridNotesIndex);
|
||||
};
|
||||
|
||||
const assertListGroupPlacement = (markup: string, groupNode: GroupNode) => {
|
||||
const listLabelIndex = markup.indexOf(groupNode.name);
|
||||
const listEditIndex = editButtonIndexForGroup(markup, groupNode.path);
|
||||
const listCountIndex = markup.indexOf("vault.groups.hostsCount", listLabelIndex);
|
||||
|
||||
assert.ok(listLabelIndex >= 0);
|
||||
assert.ok(listEditIndex > listLabelIndex);
|
||||
assert.ok(listCountIndex > listEditIndex);
|
||||
};
|
||||
|
||||
const assertGridGroupPlacement = (markup: string, groupNode: GroupNode) => {
|
||||
const gridLabelIndex = markup.indexOf(groupNode.name);
|
||||
const gridCountIndex = markup.indexOf("vault.groups.hostsCount", gridLabelIndex);
|
||||
const gridEditIndex = editButtonIndexForGroup(markup, groupNode.path);
|
||||
|
||||
assert.ok(gridLabelIndex >= 0);
|
||||
assert.ok(gridCountIndex > gridLabelIndex);
|
||||
assert.ok(gridEditIndex > gridCountIndex);
|
||||
};
|
||||
|
||||
test("VaultHostListSection keeps list edit actions beside host labels in all list sections without changing grid", () => {
|
||||
const listMarkup = renderHostList({
|
||||
viewMode: "list",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [mainHost, pinnedHost, recentHost],
|
||||
pinnedHosts: [pinnedHost],
|
||||
recentHosts: [recentHost],
|
||||
showRecentHosts: true,
|
||||
visibleDisplayedHosts: [mainHost],
|
||||
});
|
||||
|
||||
assertListHostPlacement(listMarkup, pinnedHost);
|
||||
assertListHostPlacement(listMarkup, recentHost);
|
||||
assertListHostPlacement(listMarkup, mainHost);
|
||||
|
||||
const gridMarkup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [mainHost, pinnedHost, recentHost],
|
||||
pinnedHosts: [pinnedHost],
|
||||
recentHosts: [recentHost],
|
||||
showRecentHosts: true,
|
||||
visibleDisplayedHosts: [mainHost],
|
||||
});
|
||||
|
||||
assertGridHostPlacement(gridMarkup, pinnedHost);
|
||||
assertGridHostPlacement(gridMarkup, recentHost);
|
||||
assertGridHostPlacement(gridMarkup, mainHost);
|
||||
});
|
||||
|
||||
test("VaultHostListSection keeps grouped host edit actions beside labels without changing grid", () => {
|
||||
const listMarkup = renderHostList({
|
||||
viewMode: "list",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [groupedHost],
|
||||
groupedDisplayHosts: [{ name: "Routers", hosts: [groupedHost] }],
|
||||
sortMode: "group",
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
assertListHostPlacement(listMarkup, groupedHost);
|
||||
|
||||
const gridMarkup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [groupedHost],
|
||||
groupedDisplayHosts: [{ name: "Routers", hosts: [groupedHost] }],
|
||||
sortMode: "group",
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
assertGridHostPlacement(gridMarkup, groupedHost);
|
||||
});
|
||||
|
||||
test("VaultHostListSection keeps list group edit action beside the group label without changing grid", () => {
|
||||
const listMarkup = renderHostList({
|
||||
viewMode: "list",
|
||||
displayedGroups: [group],
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
assertListGroupPlacement(listMarkup, group);
|
||||
|
||||
const gridMarkup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [group],
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
assertGridGroupPlacement(gridMarkup, group);
|
||||
});
|
||||
|
||||
test("VaultHostListSection exposes selectable groups to keyboard and assistive technology", () => {
|
||||
const markup = renderHostList({
|
||||
viewMode: "list",
|
||||
displayedGroups: [group],
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
isMultiSelectMode: true,
|
||||
selectedGroupPaths: new Set([group.path]),
|
||||
});
|
||||
|
||||
assert.match(markup, /data-group-path="production"[^>]*role="checkbox"/);
|
||||
assert.match(markup, /data-group-path="production"[^>]*aria-checked="true"/);
|
||||
assert.match(markup, /data-group-path="production"[^>]*tabindex="0"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection exposes normal group cards to keyboard", () => {
|
||||
const markup = renderHostList({
|
||||
viewMode: "list",
|
||||
displayedGroups: [group],
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
assert.match(markup, /data-group-path="production"[^>]*role="button"/);
|
||||
assert.match(markup, /data-group-path="production"[^>]*tabindex="0"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection exposes ungrouped hosts to keyboard and assistive technology", () => {
|
||||
const markup = renderHostList({
|
||||
viewMode: "list",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [mainHost],
|
||||
visibleDisplayedHosts: [mainHost],
|
||||
isMultiSelectMode: true,
|
||||
selectedHostIds: new Set([mainHost.id]),
|
||||
});
|
||||
|
||||
assert.match(markup, /data-host-id="main-host"[^>]*role="checkbox"/);
|
||||
assert.match(markup, /data-host-id="main-host"[^>]*aria-checked="true"/);
|
||||
assert.match(markup, /data-host-id="main-host"[^>]*tabindex="0"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection exposes grouped hosts to keyboard and assistive technology", () => {
|
||||
const markup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [groupedHost],
|
||||
groupedDisplayHosts: [{ name: "Production", hosts: [groupedHost] }],
|
||||
visibleDisplayedHosts: [groupedHost],
|
||||
isMultiSelectMode: true,
|
||||
selectedHostIds: new Set([groupedHost.id]),
|
||||
sortMode: "group",
|
||||
});
|
||||
|
||||
assert.match(markup, /data-host-id="grouped-host"[^>]*role="checkbox"/);
|
||||
assert.match(markup, /data-host-id="grouped-host"[^>]*aria-checked="true"/);
|
||||
assert.match(markup, /data-host-id="grouped-host"[^>]*tabindex="0"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection virtualizes large grid collections without hiding search results", () => {
|
||||
const hosts = Array.from({ length: 300 }, (_, index) => (
|
||||
makeHost(`bulk-${index}`, `Bulk ${index}`)
|
||||
));
|
||||
const markup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: hosts,
|
||||
visibleDisplayedHosts: hosts,
|
||||
});
|
||||
|
||||
const renderedHosts = (markup.match(/data-vault-grid-item="main:/g) ?? []).length;
|
||||
assert.ok(renderedHosts > 0);
|
||||
assert.ok(renderedHosts < 100);
|
||||
assert.doesNotMatch(markup, /vault\.hosts\.showMore/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection virtualizes large pinned collections", () => {
|
||||
const hosts = Array.from({ length: 300 }, (_, index) => (
|
||||
{ ...makeHost(`pinned-${index}`, `Pinned ${index}`), pinned: true }
|
||||
));
|
||||
const markup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
pinnedHosts: hosts,
|
||||
});
|
||||
|
||||
const renderedHosts = (markup.match(/data-vault-grid-item="pinned:/g) ?? []).length;
|
||||
assert.ok(renderedHosts > 0);
|
||||
assert.ok(renderedHosts < 100);
|
||||
assert.match(markup, /data-vault-virtual-collection="grid"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection virtualizes large recently connected collections", () => {
|
||||
const hosts = Array.from({ length: 300 }, (_, index) => (
|
||||
makeHost(`recent-${index}`, `Recent ${index}`)
|
||||
));
|
||||
const markup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
recentHosts: hosts,
|
||||
showRecentHosts: true,
|
||||
});
|
||||
|
||||
const renderedHosts = (markup.match(/data-vault-grid-item="recent:/g) ?? []).length;
|
||||
assert.ok(renderedHosts > 0);
|
||||
assert.ok(renderedHosts < 100);
|
||||
assert.match(markup, /data-vault-virtual-collection="grid"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection virtualizes large group collections", () => {
|
||||
const groups = Array.from({ length: 300 }, (_, index): GroupNode => ({
|
||||
name: `Group ${index}`,
|
||||
path: `group-${index}`,
|
||||
children: {},
|
||||
hosts: [],
|
||||
totalHostCount: 0,
|
||||
}));
|
||||
const markup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: groups,
|
||||
displayedHosts: [],
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
const renderedGroups = (markup.match(/data-vault-grid-item="group:/g) ?? []).length;
|
||||
assert.ok(renderedGroups > 0);
|
||||
assert.ok(renderedGroups < 100);
|
||||
assert.match(markup, /data-vault-virtual-collection="grid"/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection preserves grouped totals while virtualizing rendered cards", () => {
|
||||
const hosts = Array.from({ length: 300 }, (_, index) => (
|
||||
makeHost(`grouped-bulk-${index}`, `Grouped Bulk ${index}`)
|
||||
));
|
||||
const markup = renderHostList({
|
||||
viewMode: "grid",
|
||||
displayedGroups: [],
|
||||
displayedHosts: hosts,
|
||||
groupedDisplayHosts: [{ name: "Large group", hosts }],
|
||||
sortMode: "group",
|
||||
visibleDisplayedHosts: [],
|
||||
});
|
||||
|
||||
const renderedHosts = (markup.match(/data-vault-grid-item="grouped:/g) ?? []).length;
|
||||
assert.ok(renderedHosts > 0);
|
||||
assert.ok(renderedHosts < 100);
|
||||
assert.match(markup, /\(300\)/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection exposes hostname copy in host context menus, not as a hover button", () => {
|
||||
const source = readFileSync(new URL("./VaultHostListSection.tsx", import.meta.url), "utf8");
|
||||
assert.doesNotMatch(source, /data-vault-host-copy-hostname-button/);
|
||||
assert.doesNotMatch(source, /renderHostCopyHostnameButton/);
|
||||
assert.match(source, /terminal\.statusbar\.copyHostname\.label/);
|
||||
assert.match(source, /handleCopyHostname\(host\)/);
|
||||
assert.match(source, /!isPluginHostProtocol\(host\.protocol\)/);
|
||||
});
|
||||
|
||||
test("VaultHostListSection opens dual-pane SFTP from host context menus", () => {
|
||||
const source = readFileSync(new URL("./VaultHostListSection.tsx", import.meta.url), "utf8");
|
||||
assert.match(source, /OpenDualPaneSftpMenuItem/);
|
||||
});
|
||||
Reference in New Issue
Block a user