Ports catalog.ts's tree renderer to components under src/components/sidebar/, following docs/SIDEBAR.md: two-line cards behind a 5px activity rail, adaptive meta line (duration, detail, program, diff stat), collapsible folders, j/k walk, inline rename and a right-click menu. The nav walk reads a pure model (model.ts) rather than the DOM, so the rule that an expanded folder header is skipped and a collapsed one is not is testable without a render. Rail motion needs @keyframes, so it stays in sidebar.css rather than the Tailwind config. Closes #244
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { buildTree } from "../../tree.js";
|
|
import type { Session } from "../../types.js";
|
|
import { filterSessions, firstRowUnder, navRows, splitFolderLabel } from "./model.js";
|
|
|
|
function session(id: string, cwd: string, command = "claude"): Session {
|
|
return {
|
|
id,
|
|
cwd,
|
|
command,
|
|
env: {},
|
|
metadata: {},
|
|
status: "Running",
|
|
created_at: "2026-08-24T10:00:00Z",
|
|
} as Session;
|
|
}
|
|
|
|
describe("navRows", () => {
|
|
const tree = buildTree([session("a", "/one"), session("b", "/two")], undefined);
|
|
|
|
it("stops on every card and skips expanded folder headers", () => {
|
|
expect(navRows(tree, new Set()).map((r) => r.key)).toEqual(["session:a", "session:b"]);
|
|
});
|
|
|
|
it("stops on a collapsed folder header instead of the cards it hides", () => {
|
|
expect(navRows(tree, new Set(["one"])).map((r) => r.key)).toEqual([
|
|
"folder:one",
|
|
"session:b",
|
|
]);
|
|
});
|
|
|
|
it("names the first row an expanded folder reveals", () => {
|
|
expect(firstRowUnder(tree, new Set(), "one")?.key).toBe("session:a");
|
|
});
|
|
});
|
|
|
|
describe("splitFolderLabel", () => {
|
|
it("keeps a shallow path whole", () => {
|
|
expect(splitFolderLabel("~/maestro")).toEqual({ stem: null, last: "~/maestro" });
|
|
});
|
|
|
|
it("initials the leading segments of a deep path", () => {
|
|
expect(splitFolderLabel("~/git.naps.pt/naps62/maestro")).toEqual({
|
|
stem: "~/g/n",
|
|
last: "maestro",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("filterSessions", () => {
|
|
const all = [session("a", "/home/n/alpha", "claude"), session("b", "/home/n/beta", "bash")];
|
|
|
|
it("returns everything for a blank query", () => {
|
|
expect(filterSessions(all, " ")).toHaveLength(2);
|
|
});
|
|
|
|
it("matches the cwd and the command, case-insensitively", () => {
|
|
expect(filterSessions(all, "ALPHA").map((s) => s.id)).toEqual(["a"]);
|
|
expect(filterSessions(all, "bash").map((s) => s.id)).toEqual(["b"]);
|
|
});
|
|
});
|