import { afterAll, describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { checkFragment } from "../src/commands/doctor.ts"; import { init, TEMPLATE } from "../src/commands/init.ts"; import { discoverProjects } from "../src/core/discover.ts"; const base = mkdtempSync(join(tmpdir(), "sl0ppi-init-")); afterAll(() => rmSync(base, { recursive: true, force: true })); describe("init", () => { test("creates the marker and the fragment", () => { const dir = join(base, "proj"); mkdirSync(dir); const result = init(dir); expect(result.created).toBe(true); expect(readFileSync(result.path, "utf8")).toBe(TEMPLATE); }); test("the directory is then discoverable as a project", () => { expect(discoverProjects(base)).toContain("proj"); }); test("refuses to clobber an existing layout", () => { const dir = join(base, "existing"); mkdirSync(join(dir, ".sl0ppi"), { recursive: true }); writeFileSync(join(dir, ".sl0ppi", "layout.kdl"), "pane\n"); const result = init(dir); expect(result.created).toBe(false); expect(result.skipped).toContain("--force"); expect(readFileSync(result.path, "utf8")).toBe("pane\n"); // untouched }); test("--force overwrites", () => { const dir = join(base, "forced"); mkdirSync(join(dir, ".sl0ppi"), { recursive: true }); writeFileSync(join(dir, ".sl0ppi", "layout.kdl"), "pane\n"); const result = init(dir, { force: true }); expect(result.created).toBe(true); expect(readFileSync(result.path, "utf8")).toBe(TEMPLATE); }); test("fails clearly on a missing directory", () => { expect(() => init(join(base, "nope"))).toThrow("no such directory"); }); }); describe("the shipped template", () => { // It would be embarrassing for the thing we hand people to fail our own check. test("passes doctor's fragment check", () => { expect(checkFragment(TEMPLATE)).toBeUndefined(); }); test("declares an agent, per the design", () => { expect(TEMPLATE).toContain('purpose="agent:main"'); }); test("stages the agent instead of launching it", () => { expect(TEMPLATE).toContain("start_suspended=true"); }); test("is a fragment, not a layout", () => { const code = TEMPLATE.split("\n") .filter((line) => !line.trim().startsWith("//")) .join("\n"); expect(code).not.toContain("layout {"); expect(code).not.toContain("tab "); }); }); describe("usableSpecs", () => { // A broken fragment is one project's problem. It must not take down a root: // on session creation every tab shares one layout, so an unbalanced brace // would otherwise stop every other project opening too. test("separates openable projects from broken ones", async () => { const { usableSpecs } = await import("../src/commands/workspace.ts"); const root = join(base, "root"); mkdirSync(join(root, "good", ".sl0ppi"), { recursive: true }); mkdirSync(join(root, "bad", ".sl0ppi"), { recursive: true }); mkdirSync(join(root, "bare", ".sl0ppi"), { recursive: true }); writeFileSync(join(root, "good", ".sl0ppi", "layout.kdl"), 'pane purpose="agent:main"\n'); writeFileSync(join(root, "bad", ".sl0ppi", "layout.kdl"), "pane {\n"); const { specs, failures } = usableSpecs(root, ["good", "bad", "bare"]); expect(specs.map((s) => s.relPath)).toEqual(["good", "bare"]); // bare uses the default expect(failures).toHaveLength(1); expect(failures[0]).toContain("bad"); }); });