import { act, fireEvent, render } from "@testing-library/react"; import { afterEach, beforeEach, expect, it, vi } from "vitest"; import type { Host, HostFolder } from "@/types/ui-types"; import { SidebarTree } from "@/sidebar/tree/SidebarTree"; vi.mock("@/lib/host-connection-tabs", () => ({ resolveHostTabType: () => "terminal", })); vi.mock("@/main-axios", () => ({})); vi.mock("@/api/plugins-api", () => ({})); vi.mock("@/settings/HostPluginSections", () => ({ usePluginHostSections: () => [], })); vi.mock("@/contexts/UiPreferencesContext", () => ({ useAreaPreferences: () => ({ showResourceBars: false, rowActions: "hover" }), })); vi.mock("@/sidebar/FolderMetadataDialog", () => ({ FolderMetadataDialog: () => null, })); vi.mock("@/sidebar/HostShareModal", () => ({ HostShareModal: () => null })); vi.mock("@/sidebar/tree/FolderItem/FolderItem", () => ({ FolderItem: () =>
Folder
, folderHostCount: () => 1, })); vi.mock("@/sidebar/tree/HostItem/HostItem", () => ({ HostItem: () =>
Host
, })); const observers: Array<{ callback: ResizeObserverCallback; nodes: Set; }> = []; let hostHeight = 55; function rect(element: Element): DOMRect { const index = element.getAttribute("data-index"); const height = index === "0" ? 40 : index === "1" ? hostHeight : 600; return { x: 0, y: 0, width: 300, height, top: 0, left: 0, right: 300, bottom: height, toJSON() {}, }; } function resize(rows: Element[]) { act(() => { for (const observer of observers) { const entries = rows .filter((row) => observer.nodes.has(row)) .map( (target) => ({ target, borderBoxSize: [ { blockSize: rect(target).height, inlineSize: 300 }, ], }) as unknown as ResizeObserverEntry, ); if (entries.length) observer.callback(entries, {} as ResizeObserver); } }); } beforeEach(() => { localStorage.setItem("hostOpenFolders", JSON.stringify(["group"])); hostHeight = 55; observers.length = 0; vi.stubGlobal( "ResizeObserver", class { nodes = new Set(); constructor(callback: ResizeObserverCallback) { observers.push({ callback, nodes: this.nodes }); } observe(node: Element) { this.nodes.add(node); } unobserve(node: Element) { this.nodes.delete(node); } disconnect() { this.nodes.clear(); } }, ); vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation( function () { return rect(this); }, ); vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockReturnValue(600); vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockReturnValue(300); }); afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); localStorage.clear(); }); it("keeps an unchanged folder's measured height after changing density", () => { const host = { id: "1", name: "Host", ip: "example.com", username: "test", } as Host; const children = [ { name: "group", path: "group", children: [host] }, ] as HostFolder[]; const props = { children, onOpenTab: vi.fn(), onEditHost: vi.fn(), selectionMode: false, onToggleSelectionMode: vi.fn(), }; const view = render(); const rows = () => Array.from(view.container.querySelectorAll("[data-index]")); expect(rows()).toHaveLength(2); resize(rows()); expect((rows()[1] as HTMLElement).style.transform).toBe("translateY(40px)"); hostHeight = 30; view.rerender(); // Folder height is unchanged, so the browser only reports the host resize. resize([rows()[1]]); expect((rows()[1] as HTMLElement).style.transform).toBe("translateY(40px)"); }); it("finds a host inside a closed folder and opens the keyboard-selected match", () => { const alpha = { id: "1", name: "alpha", ip: "192.0.2.1", username: "u", } as Host; const beta = { id: "2", name: "beta", ip: "192.0.2.2", username: "u", } as Host; const onOpenTab = vi.fn(); const view = render( , ); const list = view.getByLabelText("hosts.speedSearchList"); expect(view.container.querySelectorAll("[data-index]")).toHaveLength(1); fireEvent.keyDown(list, { key: "a" }); const search = view.getByLabelText("hosts.speedSearch"); expect(document.activeElement).toBe(search); expect(view.container.querySelectorAll("[data-index]")).toHaveLength(3); fireEvent.keyDown(search, { key: "ArrowDown" }); fireEvent.keyDown(search, { key: "Enter" }); expect(onOpenTab).toHaveBeenCalledWith(beta, "terminal", { forceNewTab: false, }); fireEvent.keyDown(search, { key: "Escape" }); expect(view.queryByLabelText("hosts.speedSearch")).toBeNull(); expect(view.container.querySelectorAll("[data-index]")).toHaveLength(1); expect(document.activeElement).toBe(list); });