import { describe, expect, it } from "vitest"; import type { Host, HostFolder } from "@/types/ui-types"; import { isFolder, collectVisibleRows, buildReorderRows, rowKey, rowKind, ROOT_PARENT, } from "../../sidebar/tree/visible-rows"; function host(id: string, name: string): Host { return { id, name, username: "u", ip: "10.0.0." + id, port: 22, folder: "", online: true, cpu: null, ram: null, lastAccess: "", tags: [], authType: "password", pin: false, enableSsh: true, } as Host; } describe("collectVisibleRows", () => { const tree: (Host | HostFolder)[] = [ host("1", "root-host"), { name: "prod", path: "prod", children: [ host("2", "web"), { name: "db", path: "prod / db", children: [host("3", "postgres")], }, ], }, ]; it("collapses closed folders", () => { const rows = collectVisibleRows(tree, "", new Set()); expect( rows.map((r) => (isFolder(r.item) ? r.item.name : r.item.name)), ).toEqual(["root-host", "prod"]); }); it("expands open folders with depth", () => { const rows = collectVisibleRows(tree, "", new Set(["prod", "prod / db"])); expect( rows.map((r) => ({ name: isFolder(r.item) ? r.item.name : r.item.name, depth: r.depth, })), ).toEqual([ { name: "root-host", depth: 0 }, { name: "prod", depth: 0 }, { name: "web", depth: 1 }, { name: "db", depth: 1 }, { name: "postgres", depth: 2 }, ]); }); it("opens all matching folders when searching", () => { const rows = collectVisibleRows(tree, "postgres", new Set()); expect( rows.map((r) => (isFolder(r.item) ? r.item.name : r.item.name)), ).toEqual(["prod", "db", "postgres"]); }); }); describe("collectVisibleRows with sub-host nesting", () => { function hostWithChildren( id: string, name: string, childHosts?: Host[], ): Host { return { ...host(id, name), childHosts } as Host; } it("shows a parent host's children by default, unlike folders", () => { const tree: (Host | HostFolder)[] = [ hostWithChildren("1", "Zeus", [host("2", "vm1")]), ]; const rows = collectVisibleRows(tree, "", new Set()); expect(rows.map((r) => r.item.name)).toEqual(["Zeus", "vm1"]); expect(rows[1].depth).toBe(1); }); it("hides children once the parent is explicitly collapsed", () => { const tree: (Host | HostFolder)[] = [ hostWithChildren("1", "Zeus", [host("2", "vm1")]), ]; const rows = collectVisibleRows( tree, "", new Set(), [], 0, new Set(["host:1"]), ); expect(rows.map((r) => r.item.name)).toEqual(["Zeus"]); }); it("always shows children while searching, regardless of collapsed state", () => { const tree: (Host | HostFolder)[] = [ hostWithChildren("1", "Zeus", [host("2", "vm1")]), ]; const rows = collectVisibleRows( tree, "vm1", new Set(), [], 0, new Set(["host:1"]), ); expect(rows.map((r) => r.item.name)).toEqual(["Zeus", "vm1"]); }); }); describe("collectVisibleRows with group-by views", () => { it("gives each occurrence of a multi-tag host a distinct parentPath", () => { // Group-by-tags puts the same host under one synthetic folder per tag, // so a host with two tags shows up twice in the tree with the same id. const multiTagHost = host("9", "shared-box"); const tree: (Host | HostFolder)[] = [ { name: "prod", path: "__group__:tag:prod", children: [multiTagHost], }, { name: "web", path: "__group__:tag:web", children: [multiTagHost], }, ]; const rows = collectVisibleRows( tree, "", new Set(["__group__:tag:prod", "__group__:tag:web"]), ); const hostRows = rows.filter((r) => !isFolder(r.item)); expect(hostRows).toHaveLength(2); expect(hostRows[0].parentPath).not.toBe(hostRows[1].parentPath); }); }); describe("buildReorderRows", () => { it("keys folders by path and hosts by id", () => { const folder: HostFolder = { name: "Web", path: "Prod / Web", children: [], }; expect(rowKey(folder)).toBe("folder:Prod / Web"); expect(rowKey(host("7", "box"))).toBe("host:7"); }); it("reads the kind back off a key", () => { expect(rowKind("folder:Prod / Web")).toBe("folder"); expect(rowKind("host:7")).toBe("host"); }); it("parents a nested folder to its path prefix", () => { const nested: HostFolder = { name: "Web", path: "Prod / Web", children: [], sortOrder: 500, }; expect(buildReorderRows([{ item: nested }])).toEqual([ { key: "folder:Prod / Web", parentKey: "folder:Prod", sortOrder: 500 }, ]); }); it("parents a top-level folder to the root sentinel", () => { const top: HostFolder = { name: "Prod", path: "Prod", children: [] }; expect(buildReorderRows([{ item: top }])[0].parentKey).toBe(ROOT_PARENT); }); it("parents a host to its folder", () => { const h = { ...host("3", "db"), folder: "Prod / Web", sortOrder: 2000 }; expect(buildReorderRows([{ item: h }])).toEqual([ { key: "host:3", parentKey: "folder:Prod / Web", sortOrder: 2000 }, ]); }); it("parents a sub-host to its parent host, not its folder", () => { const child = { ...host("4", "child"), folder: "Prod", parentHostId: "3" }; expect(buildReorderRows([{ item: child }])[0].parentKey).toBe("host:3"); }); it("parents a folderless host to the root sentinel", () => { expect(buildReorderRows([{ item: host("5", "loose") }])[0].parentKey).toBe( ROOT_PARENT, ); }); });