import { describe, expect, test } from "bun:test"; import { isNoOp, planTabs, type TabInfo } from "../src/core/plan.ts"; import { projectPurpose } from "../src/core/purpose.ts"; const ROOT = "/home/user/dev"; /** Every path under the root exists unless a test says otherwise. */ function existsAll(): (p: string) => boolean { return () => true; } function existsOnly(paths: string[]): (p: string) => boolean { const set = new Set(paths); return (p) => set.has(p); } function tab(position: number, name: string, purpose?: string): TabInfo { return { position, name, purpose }; } describe("planTabs", () => { test("creates one tab per discovered project, alphabetically", () => { const plan = planTabs({ root: ROOT, rootExists: true, projects: ["zeta", "alpha", "mid"], tabs: [], pathExists: existsAll(), }); expect(plan.create.map((c) => c.relPath)).toEqual(["alpha", "mid", "zeta"]); expect(plan.close).toHaveLength(0); }); // THE property. If this ever fails, the tool is worse than useless. test("is a no-op when every project already has its tab", () => { const projects = ["alpha", "mid", "zeta"]; const tabs = projects.map((p, i) => tab(i, p, projectPurpose(p))); const plan = planTabs({ root: ROOT, rootExists: true, projects, tabs, pathExists: existsAll(), }); expect(isNoOp(plan)).toBe(true); expect(plan.keep).toHaveLength(3); }); test("applying the plan twice converges (second run is a no-op)", () => { const projects = ["alpha", "beta"]; let tabs: TabInfo[] = []; const first = planTabs({ root: ROOT, rootExists: true, projects, tabs, pathExists: existsAll() }); // simulate applying it tabs = first.create.map((c, i) => tab(i, c.relPath, c.purpose)); const second = planTabs({ root: ROOT, rootExists: true, projects, tabs, pathExists: existsAll() }); expect(first.create).toHaveLength(2); expect(isNoOp(second)).toBe(true); }); test("creates only what is missing", () => { const plan = planTabs({ root: ROOT, rootExists: true, projects: ["alpha", "beta"], tabs: [tab(0, "alpha", projectPurpose("alpha"))], pathExists: existsAll(), }); expect(plan.create.map((c) => c.relPath)).toEqual(["beta"]); expect(plan.keep).toHaveLength(1); }); test("closes a tab whose project directory is gone", () => { const plan = planTabs({ root: ROOT, rootExists: true, projects: ["alpha"], tabs: [tab(0, "alpha", projectPurpose("alpha")), tab(1, "deleted", projectPurpose("deleted"))], pathExists: existsOnly([`${ROOT}/alpha`]), }); expect(plan.close.map((t) => t.name)).toEqual(["deleted"]); }); // The mount guard: an unmounted root looks exactly like mass deletion. test("closes NOTHING when the root itself is missing", () => { const plan = planTabs({ root: ROOT, rootExists: false, projects: [], tabs: [ tab(0, "alpha", projectPurpose("alpha")), tab(1, "beta", projectPurpose("beta")), ], pathExists: existsOnly([]), }); expect(plan.close).toHaveLength(0); expect(plan.keep).toHaveLength(2); }); test("keeps an existing project that simply was not discovered (nested / marker removed)", () => { const plan = planTabs({ root: ROOT, rootExists: true, projects: [], tabs: [tab(0, "nested", projectPurpose("group/nested"))], pathExists: existsOnly([`${ROOT}/group/nested`]), }); expect(plan.close).toHaveLength(0); expect(plan.keep).toHaveLength(1); }); test("never touches tabs without a project purpose", () => { const plan = planTabs({ root: ROOT, rootExists: true, projects: ["alpha"], tabs: [tab(0, "scratch"), tab(1, "notes", "human:notes")], pathExists: existsOnly([`${ROOT}/alpha`]), }); expect(plan.close).toHaveLength(0); expect(plan.foreign.map((t) => t.name)).toEqual(["scratch", "notes"]); expect(plan.create.map((c) => c.relPath)).toEqual(["alpha"]); }); // A purpose that had to be truncated+hashed cannot be turned back into a // path, so we must not "verify" it and close the tab. test("keeps a tab whose purpose cannot be reconstructed", () => { const long = "a".repeat(120); const plan = planTabs({ root: ROOT, rootExists: true, projects: [], tabs: [tab(0, "long", projectPurpose(long))], pathExists: existsOnly([]), // nothing exists }); expect(plan.close).toHaveLength(0); }); });