aboutsummaryrefslogtreecommitdiffstats
path: root/cli/test/init.test.ts
blob: 0ab4cdec8264adc5a8aed4159dead7fdd664edea (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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");
  });
});